Kotlin Uncovered: Part 5

Learning about Kotlin extension functions through decompilation

wocintech stock - 6 by WOCinTech Chat is licensed under Creative Commons 2.0 Generic

I’ve been going through different Kotlin features and decompiling the bytecode into Java to understand some of the inner workings. So far I’ve shared how Kotlin works with data classes and the null safe type system. In this post, we’re going to look at extension functions.

In Java, we’ve adopted a pattern of creating static utility classes to create new global functionality around a type. Many of the methods that we put in these classes often feel like they should be on the class itself. An example of this is TextUtils.isEmpty(String string);. Our code can become littered with these calls. Thankfully, Kotlin has a solution.

Using extension functions, we can add new methods that can be called directly on an instance of the extended class without inheriting or wrapping it. You can even extend final classes like String! Since this is much different from what we’re used to in Java, let’s see what’s going on behind the scenes. We’ll make our own version of isEmpty().

To declare the extension function we take the class name and dot it to the function name. String?.isEmpty() here. The rest of the signature is the same as any other Kotlin function. Then, in the body we can reference the String we’re calling it on (the receiver) with this. We then put all this in a file called StringExt.kt.

// StringExt.kt
fun String?.isEmpty(): Boolean {
  return this == null || this.length == 0
}

Then, when we call it, it looks like we’re calling a method that has always just belonged to the class. This might not be unexpected in an interpreted language, but it’s news coming from Java. Notice that we put the function on the nullable String?, so we can make this check even on null. Look how nice and clean that is!

null.isEmpty()    // true
"".isEmpty()      // true
"hello".isEmpty() // false

Time to decompile the bytecode to see what is going on here. By doing so, we discover that it is making a class with a static method, similar to how we would with a utility class. We have the class named StringExtKt (the same as our Kotlin file name), and the static method that takes a String.

public final class StringExtKt {
  public static final boolean isEmpty(@Nullable String $receiver) {
    return $receiver == null || $receiver.length() == 0;
  }
}

When we decompile the Kotlin code that calls the extension function, we see that it’s calling a static method on a utility class and passing the receiver as the first argument. In fact, this is exactly how we would use the same Kotlin extension method if we were calling it from our Java code.

StringExtKt.isEmpty((String)null); // true
StringExtKt.isEmpty("");           // true
StringExtKt.isEmpty("hello");      // false

If we wanted to control the name of this class, we could annotate our Kotlin code with @file:JvmName("StringUtils"). By doing so, the created class would be StringUtils instead of StringExtKt.

Kotlin extension functions have done wonders for cleaning up our code. They make reading and writing code so much easier. One of my favorite places we’ve used them is when finding bounded times. We went from:

DateTimeUtils.plusInterval(DateTimeUtils.startBoundary(dateTime, interval), interval);

to

dateTime.startBoundary(interval).plusInterval(interval)

…making our code much more readable.

What are some of your favorite applications of extension functions?


Need to go back and refresh your memory on other parts of the series? You can find them below:

Part One

Part Two

Part Three

Part Four

Photo of Victoria Gonda

Victoria is a software developer working on mobile and full stack web applications. She enjoys exchanging knowledge through conference talks and writing.

Comments:


Post a Comment

(optional)
(optional — will be included as a link.)