article banner

Extensions in Kotlin

This is a chapter from the book Kotlin Essentials. You can find it on LeanPub or Amazon. It is also available as a course.

The most intuitive way to define methods and properties is inside classes. Such elements are called class members or, more concretely, member functions and member properties.

class Telephone( // member property val number: String ) { // member function fun call() { print("Calling $number") } } fun main() { // Usage val telephone = Telephone("123456789") println(telephone.number) // 123456789 telephone.call() // Calling 123456789 }

On the other hand, Kotlin allows another way to define functions and properties that are called on an instance: extensions. Extension functions are defined like regular functions, but they additionally have an extra type (and dot) before the function name. In the example below, the call function is defined as an extension function on Telephone, so it needs to be called on an instance of this type.

class Telephone( val number: String ) fun Telephone.call() { print("Calling $number") } fun main() { // Usage val telephone = Telephone("123456789") telephone.call() // Calling 123456789 }

Both member functions and extension functions are referred to as methods.

Extension functions can be defined on types we don’t control, for instance String. This gives us the power to extend external APIs with our own functions.

fun String.remove(value: String) = this.replace(value, "") fun main() { println("Who Framed Roger Rabbit?".remove(" ")) // WhoFramedRogerRabbit? }

Take a look at the example above. We defined the extension function remove on String, so we need to call this function on an object of type String. Inside the function, we reference this object using the this keyword, just like inside member functions. The this keyword can also be used implicitly.

// explicit this fun String.remove(value: String) = this.replace(value, "") // implicit this fun String.remove(value: String) = replace(value, "")

The this keyword is known as the receiver. Inside extension functions, we call it an extension receiver. Inside member functions, we call it a dispatch receiver. The type we extend with the extension function is called the receiver type.

Extension functions behave a lot like member functions. When developers learn this, they are often concerned about objects' safety, but this isn’t a problem as extensions do not have any special access to class elements. The only difference between top-level extension functions and other top-level functions is that they are called on an instance instead of receiving this instance as a regular argument. To see this more clearly, let's take a look under the hood of extension functions.

Extension functions under the hood

To understand extension functions, let's again use "Tools > Kotlin > Show Kotlin bytecode" and "Decompile" (as explained in chapter Your first program in Kotlin in section What is under the hood on JVM?). We will compile and decompile to Java our remove function definition and its call:

fun String.remove(value: String) = this.replace(value, "") fun main() { println("A B C".remove(" ")) // ABC }

As a result, you should see the following code:

public final class PlaygroundKt {
    @NotNull
    public static final String remove(
            @NotNull String $this$remove,
            @NotNull String value
    ) {
        // parameters not-null checks
        return StringsKt.replace$default(
                $this$remove,
                value,
                ""
                // plus default values
        );
    }

    public static final void main(@NotNull String[] args) {
        // parameter not-null check
        String var1 = remove("A B C", " ");
        System.out.println(var1);
    }
}

Notice what happened to the receiver type: it became a parameter. You can also see that, under the hood, remove is not called on an object. It is just a regular static function.

When you define an extension function, you do not really add anything to a class. It is just syntactic sugar. Let's compare the two following implementations of remove.

fun remove(text: String, value: String) = text.replace(value, "") fun String.remove(value: String) = this.replace(value, "")

Under the hood, they are nearly identical. The difference is in how Kotlin expects them to be called. Regular functions receive all their arguments in regular argument positions. Extension functions are called "on" a value.

Extension properties

An extension cannot hold a state, so it cannot have fields. Although properties do not need fields, they can be defined by their getters and setters. This is why we can define extension properties if they do not need a backing field and are defined by accessors.

val <T> List<T>.lastIndex: Int get() = size - 1

Extension properties are very popular on Android, where accessing different services is complex and repetitive. Defining extension properties lets us do this much more easily.

val Context.inflater: LayoutInflater get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater val Context.notificationManager: NotificationManager get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val Context.alarmManager: AlarmManager get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager

Extension properties can define both a getter and a setter. Here is an extension property that provides a different representation of a user birthdate:

class User { // ... var birthdateMillis: Long? = null } var User.birthdate: Date? get() { val millis = birthdateMillis return if (millis == null) null else Date(millis) } set(value) { birthdateMillis = value?.time }

Extensions vs members

The biggest difference between members and extensions in terms of use is that extensions need to be imported separately. For this reason, they can be located in a different package. This fact is used when we cannot add a member ourselves. It is also used in projects designed to separate data and behavior. Properties with fields need to be located in a class, but methods can be located separately as long as they only access the public API of the class.

Thanks to the fact that extensions need to be imported, we can have many extensions with the same name for the same type. This is good because different libraries can provide extra methods without causing a conflict. On the other hand, it would be dangerous to have two extensions with the same name but different behaviors. If you have such a situation, it is a code smell and is a clue that someone has abused the extension function capability.

Another significant difference is that extensions are not virtual, meaning that they cannot be redefined in derived classes. This is why if you have an extension defined on both a supertype and a subtype, the compiler decides which function is chosen based on how the variable is typed, not what its actual class is.

open class View class Button : View() fun View.printMe() { println("I'm a View") } fun Button.printMe() { println("I'm a Button") } fun main() { val button: Button = Button() button.printMe() // I'm a Button val view: View = button view.printMe() // I'm a View }

The behavior of extension functions is different from member functions. Member functions are virtual, so up-casting the type of an object does not influence which member function is chosen.

open class View { open fun printMe() { println("I'm a View") } } class Button: View() { override fun printMe() { println("I'm a Button") } } fun main() { val button: Button = Button() button.printMe() // I'm a Button val view: View = button view.printMe() // I'm a Button }

This behavior is the result of the fact that extension functions are compiled under the hood into normal functions in which the extension’s receiver is placed as the first argument:

open class View class Button : View() fun printMe(view: View) { println("I'm a View") } fun printMe(button: Button) { println("I'm a Button") } fun main() { val button: Button = Button() printMe(button) // I'm a Button val view: View = button printMe(view) // I'm a View }

Another consequence of what extensions are under the hood is that we define extensions on types, not on classes. This gives us more freedom. For instance, we can define an extension on a nullable or generic type:

inline fun CharSequence?.isNullOrBlank(): Boolean { // (skipped contract definition) return this == null || this.isBlank() } fun Iterable<Int>.sum(): Int { var sum: Int = 0 for (element in this) { sum += element } return sum }

The last important difference is that extensions are not listed as members in the class reference. This is why they are not considered by annotation processors; it is also why, when we process a class using annotation processing, we cannot extract elements that should be processed into extensions. On the other hand, if we extract non-essential elements into extensions, we don’t need to worry about them being seen by those processors. We don’t need to hide them because they are not in the class they extend anyway.

Extension functions on object declarations

We can define extensions on object declarations.

object A fun A.foo() {} fun main() { A.foo() val a: A = A a.foo() }

To define an extension function on a companion object, we need to use the companion object's real name. If this name is not set explicitly, the default one is "Companion". To define an extension function on a companion object, this companion object must exist. This is why some classes define companion objects without bodies.

class A { companion object } fun A.Companion.foo() {} fun main() { A.foo() val a: A.Companion = A a.foo() }

Member extension functions

It is possible to define extension functions inside classes. Such functions are known as member extension functions.

class Telephone { fun String.call() { // ... } }

Member extension functions are considered a code smell, and we should avoid using them if we don’t have a good reason. For a deeper explanation, see Effective Kotlin, Item 46: Avoid member extensions.

Use cases

The most important use-case for extensions is adding methods and properties to APIs that we don't control. A good example is displaying a toast or hiding a view on Android. Both these operations are unnecessarily complicated, so we like to define extensions to simplify them.

fun Context.toast(message: String) { Toast.makeText(this, message, Toast.LENGTH_LONG).show() } fun View.hide() { this.visibility = View.GONE }

However, there are also cases where we prefer to use extensions instead of members. Consider the Iterable interface, which has only one member function, iterator; however, it has many methods, which are defined in the standard library as extensions1, like onEach or joinToString. The fact that these are defined as extensions allows for smaller and more concise interfaces.

interface Iterable<out T> { operator fun iterator(): Iterator<T> }

Extension functions are also more elastic than regular functions. This is mainly because they are defined on types, so we can define extensions on types like Iterable<Int> or Iterable<T>.

fun <T : Comparable<T>> Iterable<T>.sorted(): List<T> { if (this is Collection) { if (size <= 1) return this.toList() @Suppress("UNCHECKED_CAST") return (toTypedArray<Comparable<T>>() as Array<T>) .apply { sort() } .asList() } return toMutableList().apply { sort() } } fun Iterable<Int>.sum(): Int { var sum: Int = 0 for (element in this) { sum += element } return sum }

In bigger projects, we often have similar classes for different parts of our application. Let's say that you implement a backend for an online shop, and you have a class Product to represent all the products.

import java.math.BigDecimal class Product( val id: String, val title: String, val imgSrc: String, val description: String, val price: BigDecimal, val type: ProductType, // ... )

You also have a similar (but not identical) class called ProductJson, which is used to represent the objects you use in your application API responses or that you read from API requests.

class ProductJson( val id: String, val title: String, val img: String, val desc: String, val price: String, val type: String, // ... )

Instances of Product are used in your application, and instances of ProductJson are used in the API. These objects need to be separated because, for instance, you don’t want to change your API response when you change a property name in an internal class. Yet, we often need to transform between Product and ProductJson. For this, we could define a member function toProduct.

class ProductJson( val id: String, val title: String, val img: String, val desc: String, val price: String, val type: String, // ... ) { fun toProduct() = Product( id = this.id, title = this.title, imgSrc = this.img, description = this.desc, price = BigDecimal(price), type = enumValueOf<ProductType>(this.type) ) }

Not everyone likes this solution as it makes ProductJson bigger and more complicated. It is also not useful in transforming from Product to ProductJson because in most modern architectures we don’t want domain classes (like Product) to be aware of details such as their API representation. A better solution is to define both toProduct and toProductJson as extension functions, then locate them together next to the ProductJson class. It is good to locate those transformation functions next to each other, because they have a lot in common.

class ProductJson( val id: String, val title: String, val img: String, val desc: String, val price: String, val type: String, // ... ) fun ProductJson.toProduct() = Product( id = this.id, title = this.title, imgSrc = this.img, description = this.desc, price = BigDecimal(this.price), type = enumValueOf<ProductType>(this.type) ) fun Product.toProductJson() = ProductJson( id = this.id, title = this.title, img = this.imgSrc, desc = this.description, price = this.price.toString(), type = this.type.name )

This seems to be a popular pattern, both on the backend and in Android applications.

Summary

In this chapter, we've learned about extensions - a powerful Kotlin feature that is often used to create convenient and meaningful utils and to control our code better. However, with great power comes great responsibility. We should not be worried about using extensions, but we should use them consciously and only where they make sense.

In the next chapter, we will finally introduce collections so that we can talk about lists, sets, maps, and arrays. There’s a lot ahead, so get ready.

1:

Roman Elizarov (Project Lead for the Kotlin Programming Language) refers to this as an extension-oriented design in the standard library. Source: elizarov.medium.com/extension-oriented-design-13f4f27deaee