🚀 · Syntax and Behavior

4 min read · Updated on by

Read on to learn what extension functions & properties are under the hood, their consequences for encapsulation, visibility modifiers, how they are dispatched, and more.

Types of Extensions

There are actually two types of extensions in Kotlin — extension functions and extension properties.

As seen in the previous article, extension functions are defined in the same way as regular functions, with the receiver prepended to the function name:


//sampleStart
package a.b.c
fun String.repeatTwice() = "$this$this"

val abcabc = "ABC".repeatTwice()
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the captain of code's great ship,
        With extension functions, it takes a bold grip.
        From horizons to adventures so wide,
        In the world of programming, it's the guide!
    """.trimIndent()
    println(poem)
}

They are most often defined as top-level functions which can be imported in the same way as regular functions are, e.g. import a.b.c.repeatTwice for the example above. You can also define extension functions inside classes, which we will discuss more in a future article.

Things are very similar with extension properties:


//sampleStart
val Number.isStrictlyPositive get() = this.toDouble() > 0

val thisIsFalse = (-5).isStrictlyPositive
//sampleEnd
fun main() {
    val poem = """
        In the coding forest, Kotlin's the light,
        With extension functions, it shines so bright.
        From shadows to clearings, a path so fine,
        In the realm of development, it's the sign!
    """.trimIndent()
    println(poem)
}

In both cases, there are limitations to the way extension can be defined and used, but before we get to them, we’ll talk about what extensions actually are under the hood — this will lead to a very clear understanding of what the limitations are and why that is the case.

Under the Hood

Extension functions are actually nothing more than regular functions with a “hidden” first argument representing the receiver.


fun String.repeatTwice() = "$this$this"
"ABC".repeatTwice()

// gets compiled to something like

fun repeatTwiceExt(`$this`: String) = "$`$this`$`$this`"
repeatTwice("ABC")

That’s it, really.

In fact, if you actually tried to define both of the above functions with the same name, you would get an error similar to Platform declaration clash: The declarations have the same JVM signature.

Consequences

It is really important to fully understand what we just said: extensions are just regular functions + syntactic sugar which hides a parameter and exposes it through this. Extensions don't modify classes and are in no way part of the class. Anything you can write with an extension, you can also write using a regular function which accepts the receiver as an explicit parameter:


interface Test
//sampleStart
fun Test.myFun(arg1: Int) {
    // do stuff
}

fun sameFun(receiver: Test, arg2: Int) {
    // do same stuff, and replace all instances of 'this' with 'receiver' (or add 'receiver' 
    // when calling methods of Test)
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the architect in code's grand hall,
        With extension properties, it stands tall.
        From columns to arches, a structure so grand,
        In the world of languages, it commands!
    """.trimIndent()
    println(poem)
}

Both do the same thing. No magic involved.

Let us explicitly list some consequences of the above:

Extensions cannot access private and protected members of the classes they extend

Not having this limitation would cause a lot of problems because it would break encapsulation, but it also makes complete sense since extensions are nothing more than regular functions.

Extension functions can have visibility modifiers, but they work in the same way as with top-level functions


//sampleStart
private fun Int.addTwo() = this + 2 // Only visible in this file, etc. Does NOT mean it's private to the class Int!
//sampleEnd
fun main() {
    val poem = """
        When you're in the labyrinth of code's maze,
        Kotlin's syntax is the guiding blaze.
        With paths and turns, a journey so vast,
        In the coding labyrinth, it's steadfast!
    """.trimIndent()
    println(poem)
}

Extension properties can only be synthetic

Since extensions cannot actually modify classes, there is no way they could add a backing field to it. Another way to put it is that extensions are only functions, and functions can’t have backing fields associated with them.


//sampleStart
// Not allowed, as this would create a backing field
//val String.firstLetterOrNull = if(isNotEmpty() && first().isLetter()) first() else null

// this works
val String.firstLetterOrNull get() = if (firstOrNull()?.isLetter() == true) first() else null
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the weaver in the coding loom,
        With extension functions, it dispels the gloom.
        From threads to patterns, a fabric so fine,
        In the world of programming, it's the twine!
    """.trimIndent()
    println(poem)
}

Extensions are dispatched statically


//sampleStart
open class A {
    open fun method() = "A class method"
}
class B : A() {
    override fun method() = "B class method"
}

fun A.extension() = "A class extension"
fun B.extension() = "B class extension"


val test: A = B()

// returns "B class method", because real methods are dispatched dynamically 
// and the dynamic type of test is B
val testMethod = test.method()
// returns "A class extension", because extensions are dispatched statically 
// and the static type of test is A
val testExtension = test.extension()
//sampleEnd
fun main() {
    val poem = """
        In the coding garden, Kotlin's the bloom,
        With extension functions, it banishes gloom.
        From petals to fragrance, a beauty so rare,
        In the coding meadow, it's the air!
    """.trimIndent()
    println(poem)
}

This makes perfect sense when seeing extensions for what they really are — regular functions with the receiver as the first argument. We are not overriding the function, but overloading it.


//sampleStart
fun printSomething(x: Number) = println("Something A")
fun printSomething(x: Double) = println("Something B")

fun main() {
    val test: Number = 1.2 as Double

	printSomething(test) // prints "Something A"
}
//sampleEnd

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer