How many times have you wished there was a simpler way to add just a small piece of functionality to a class in Java? Whenever that happened, you might have ended up either extending the class or utility static method. Both works fine, but it feels like there should be something more convenient, right?

If you’re writing Kotlin, though, there is such thing and it’s called extension functions, as you might have already known (or guessed, at this point). They allow you to add behavior to a class without the need of getting to its source code, since an extension function can be declared outside the scope of its class. And by doing so, they also improve the readability of your code. Awesome!

What is an extension function?

Extension functions don’t modify the byte code of class they are adding behavior to. What they do is a really simple trick: an extension function is just a method which takes the receiver (which is the object of the class you’re calling the method on) as its first argument, plus any additional parameter you have explicitly specified. Also, if they are declared as top level functions, they are compiled as static methods in the byte code.

Let’s start with the simplest example:

fun Activity.toast(text: String) {
    ...
}

If you take a look at the decompiled Java version, the previous method is:

public static final void toast(@NotNull Activity $receiver, @NotNull String text) {
    ...
}

In case you needed further proofs, this becomes clear when you’re invoking the method from Java: you won’t be able to call the function as a method of the class, but you can still take advantage of it as a static method, with the receiver as a parameter, like:

ActivityExtKt.toast(this, "Hello!");

The nature of this feature is also the reason why you are able to create an extension function on a nullable type and then invoke it without using the ?.notation. For example, one of the extension functions included in the Kotlin standard library allows us to run a line like println(null.toString()), because what you’re actually doing is:

fun Any?.toString() : String {
    if (this == null) return "null" 
    return toString()
}

Which is the equivalent of the perfectly legal:

public static toString(@Nullable Object obj) {
    if (obj == null) {
        return "null";
    }   
    return obj.toString();
}

Static vs dynamic dispatching

Before we dive deeper into extension functions, it’s important to have clear in mind the distinction between static and dynamic dispatching. As you know, Java is a statically typed language and every object you create has got not just a runtime, but also a compile time type, which the developer has to specify explicitly (or it can be inferred in Kotlin).

When we say Base base = new Extended(), we are declaring a variable called base, which has got compile time type Base and runtime type Extended. When we call base.foo() the method will be dispatched dynamically, which means the runtime type (Extended) method will be invoked.

When we call an overloaded method though, the dispatching becomes static and depends only on the compile time type:

void foo(Base base) {
    ...
}
void foo(Extended extended) {
    ...
}
public static void main(String[] args) {
    Base base = new Extended();
    foo(base);
}

In this case calling the invoked method will the first one, even the object is actually an instance of Extended.

Extension functions are always dispatched statically

What does all this have to do with the topic of this post, which is extension functions? Since the receiver is actually just a parameter of the compiled method in the byte code, you will be able to overload it but not to override it. This is probably the most important difference between a member and an extension function: whereas the former are dispatched dynamically, the latter are always statically.

Extension functions are always dispatched statically

To better grasp what the previous statement means, let’s take a look at the next example:

open class Base
class Extended: Base()
fun Base.foo() = "Base!"
fun Extended.foo() = "Extended!"
fun main(args: Array<String>) {
    val instance: Base = Extended()
    val instance2 = Extended()
    println(instance.foo())
    println(instance2.foo())
}

As we said, since only the compile time type is taken into account, the first print will invoke the Base.foo() method, while the second one the Extended.foo() one.

Base!
Extended!

Extension functions inside a class

If you declare an extension function inside a class, it won’t be static and you’ll be able to even override it if it’s declared as open. Does this mean it will be dynamically dispatched?

This is where it gets tricky: when an extension function is declared in a class, it has got both a dispatch receiver and an extension receiver.

class A {
    fun B.foo() = "Hello!"
}

In the case above, A is the dispatch receiver and B is the extension receiver. If you declare an extension function as open, it’s dispatching can be dynamic only in regards to the dispatch receiver, while the extension receiver is always resolved at compile time.

Let’s try to grasp this concept with an example:

open class Base
class Extended : Base()
open class A {     
    open fun Base.foo() { 
        println("Base.foo in A")     
    }
    open fun Extended.foo() { 
        println("Extended.foo in A")
    }
    fun caller(base: Base) {
         base.foo()  
    } 
}
class B : A() {
    override fun Base.foo() { 
        println("Base.foo in B")
     }
    override fun Extended.foo() { 
        println("Extended.foo in B")
     } 
}
A().caller(Base())   // prints "Base.foo in A"
B().caller(Base())  // prints "Base.foo in B" - dispatch receiver is resolved dynamically
A().caller(Extended())  // prints "Base.foo in A" - extension receiver is resolved statically

As you might notice, the Extended extension function is never invoked, and this behavior is aligned with what we have seen before in term of static dispatching. What actually changes is which of the two Base extension functions is called, depending on the runtime type of the class which invokes caller.

Extension functions vs member functions

Let’s have another example:

class Person {
    fun walk() {
        ...
    }
    fun Person.walk() {
        ...
    }
}
fun Person.walk() {
    ...
}

You might expect this code not to compile because of a signature conflict, but actually it’s perfectly correct, even if useless, since only the first one will ever be used and the two extensions will be ignored. The reason is that member functions always win on their extension counterparts. Luckily, IntelliJ IDEA is kind enough to let us know with an inspection that the extension is shadowed by the class method.

Visibility

Last but not least, inside an extension function you will be able to access only the public properties and methods:

class Hero(
   val name: String,
   protected val surname: String,
   private val nickname: String
)
fun Hero.getIdentity() = "$name $surname, $nickname"

This is probably not a big surprise, since we already know now that the code above generates a method which takes an instance of Hero as a input, so of course it will be able to access only its public fields.

The same wouldn’t be true if the extension was declared inside the class, of course, but then you should probably declare it as a member function.

Wrap up

From the previous examples we have seen that an extension function:

  • Can’t be overridden if it’s a top level function
  • Can’t override a member function
  • Can’t access non-public properties of its receiver
  • Is always dispatched statically with regards to the extension receiver type

Understanding how extension functions work is the first step to take the most out of such an amazing feature.

This tutorial was reposted from Medium with modifications