Things to Know about Kotlin Extension Functions

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:

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:

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:

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:

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.

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

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:

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.

Resources

This website uses cookies to improve your experience and to personalise content and adverts, to provide social media features and to analyse traffic. We'll assume you're ok with this, but you can opt-out if you wish. AcceptRejectRead More