Baeldung Pro – Kotlin – NPI EA (cat = Baeldung on Kotlin)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

1. Overview

Extensions in Kotlin are one of the standout features that make the language more flexible and modern. Extensions allow us to add new functions or properties to existing classes without having to change or inherit the class.

We already have extension functions and extension properties. But is it possible to create extension fields in Kotlin? This tutorial aims to explore and answer that question.

2. Properties vs. Fields

Before discussing extension fields, let’s first understand the distinction between properties and fields in Kotlin.

The properties and fields terminology can sometimes be a bit confusing because, Kotlin classes do not expose fields directly, but use properties instead.

Properties are public getter and setter functions that control access to private fields in a class.

Fields are private variables where memory is allocated. They’re accessed through properties.

In Kotlin, we can declare properties with var or val.

Let’s see an example class where model is a property:

class Car {
    var model: String = "Toyota Supra"
}

When we access model, we seem to be accessing a field, but it’s actually a property:

val car = Car()
assertEquals("Toyota Supra", car.model)

When we declare a property, Kotlin implicitly creates the field, getter, and setter functions automatically for us.

Let’s take a look at the code after we decompile it (Intellij IDEA: Tools → Kotlin → Show Kotlin Bytecode → Decompile):

public static final class Car {
    @NotNull
    private String model = "Toyota Supra";

    @NotNull
    public final String getModel() {
        return this.model;
    }

    public final void setModel(@NotNull String var1) {
        Intrinsics.checkNotNullParameter(var1, "<set-?>");
        this.model = var1;
    }
}

In Kotlin, these fields are called backing fields.

Nevertheless, we can still define the backing field explicitly:

class Car {
    private var _model: String = "Toyota Supra"
    
    var model: String
        get() = _model
        set(value) {
            _model = value
        }
}

Here, we use the _model that we created ourselves as a backing field.

3. Extensions in Kotlin

Extensions can make our code easier to read and maintain by adding functionality to existing classes.

For example, to add a new function to String, we can write an extension like:

fun String.toTitleCase(): String = this.split(" ").joinToString(" ") { it.capitalize() }

val word = "baeldung kotlin"

assertEquals("Baeldung Kotlin", word.toTitleCase())

This extension function adds a toTitleCase() method to the String class, which capitalizes the first letter of each word.

Kotlin also supports extension properties, much like it supports functions.

So, let’s try another case, using an extension property:

var String.x : Int = 0 // not allowed

Unfortunately, we’ll get an error message:

Extension property cannot be initialized because it has no backing field

Unlike regular properties, Kotlin does not create backing fields for extension properties, meaning Kotlin does not natively allow for extension fields.

4. Alternatives to Extension Fields

Extensions were not designed in Kotlin to have additional state, so any solution that emulates this will have some drawbacks.

Besides, Kotlin offers better alternatives for adding functionality without modifying the original definition of the class. So, we can use other approaches that don’t conflict with the basic principles of Kotlin.

4.1. Using a Map as a Backing Field

Some people might suggest using a mutable Map as a backing field:

val externalMap = mutableMapOf<Person, String>()

var Person.address: String
    get() = externalMap[this]?: ""
    set(value) {
        externalMap[this] = value
    }

This seems to work quite well:

val person = Person("Hangga Aji Sayekti", 35)

person.address = "Jln. Kemasan Kotagede"
assertEquals("Jln. Kemasan Kotagede", person.address)

person.address = "Jln. Kalasan Sleman"
assertEquals("Jln. Kalasan Sleman", person.address)

But if a Person object is not removed from the externalMap when it is no longer needed, this can lead to a memory leak. The object will remain in memory as long as there is a reference to it in the externalMap.

Consequently, the more objects are created and put into the externalMap, the greater the risk that the application will consume excess memory without releasing objects that are no longer needed. If we can’t handle this memory issue well, then we do not recommend this approach.

4.2. Using Extension Properties

As we know, Kotlin also supports extension properties much like it supports functions.

So, we can add a new property to the Person class without modifying the original definition of the class:

class Person(var name: String, var age: Int)

val Person.isAdult: Boolean
    get() = this.age >= 18

val Person.details: String
    get() = "Name: ${this.name}, Age: ${this.age}, isAdult: ${this.isAdult}"

The isAdult is an extension property added to the Person class. This property does not store data but uses a getter to calculate whether a person is over 18.

Let’s also try using setters and getters:

var Person.ageInDecades: Int
    get() = this.age / 10
    set(value) {
        this.age = value * 10
    }

Let’s prove it with a test:

person.ageInDecades = 1;
assertEquals("Name: Hangga Aji Sayekti, Age: 10, isAdult: false", person.details)

We cannot add new fields; however, we can use existing fields. For example, in ageInDecades, we can get and set a value backed by the existing age field.

4.3. Using the Decorator Pattern

If we want an alternative to extension fields, we can also use the decorator pattern approach.

This allows us to enhance the functionality of an object without changing the original class.

Let’s see an example showing how we could use the decorator pattern as an alternative to extension fields:

class Person(var name: String, var age: Int)

interface PersonDecorator {
    var address: String
    var person: Person
    fun getDetails(): String
}

Here, we create an interface that declares an address property, a person object of type Person, and a getDetails() function to return detailed information about the person.

Then, we need to create a class to implement it:

class PersonWithAddress(override var person: Person) : PersonDecorator {
    private var _address: String = ""

    override var address: String
        get() = _address
        set(value) {
            _address = value
        }

    override fun getDetails(): String {
        return "Name: ${person.name}, Age: ${person.age}, Address: $address"
    }
}

We can say that PersonWithAddress is a decorator that extends the functionality of Person by adding an address property and a function to display complete information.

Now, let’s write a simple test:

val person = Person("Hangga Aji Sayekti", 35)

val personWithAddress = PersonWithAddress(person)

personWithAddress.address = "Jln. Kemasan Kotagede"
assertEquals("Name: Hangga Aji Sayekti, Age: 35, Address: Jln. Kemasan Kotagede", personWithAddress.getDetails())

personWithAddress.address = "Jln. Kalasan Sleman"
assertEquals("Name: Hangga Aji Sayekti, Age: 35, Address: Jln. Kalasan Sleman", personWithAddress.getDetails())

This proves that every time the address is changed, the value is saved, and getDetails() merges the updated name, age, and address.

This could be an alternative, but we’d have to create a new decorator for each class we want to enhance.

4.4. Using Inheritance

We can also use inheritance by extending Person. In this way, we can add new properties with getters and setters and store values.

First, we must mark the Person class with the open keyword, because by default, all classes in Kotlin are final:

open class Person(var name: String, var age: Int)

Then, we add a new property called jobtitle:

class PersonExtended(name: String, age: Int) : Person(name, age) {
    var jobtitle: String = ""
}

This simple test will prove that the jobtitle property stores the passed value:

val personExtended = PersonExtended("Hangga Aji Sayekti", 35)

personExtended.jobtitle = "Software Engineer"
assertEquals("Software Engineer", personExtended.jobtitle)

personExtended.jobtitle = "Data Scientist"
assertEquals("Data Scientist", personExtended.jobtitle)

personExtended.jobtitle = "Mathematicians"
assertEquals("Mathematicians", personExtended.jobtitle)

This could be an alternative, but unfortunately, we must create a new subclass every time we want to add a new property.

5. Conclusion

In this article, we learned that Kotlin doesn’t support extension fields because it only creates backing fields for properties, not for extension properties.

We also discussed some alternatives to add functionality without having to modify the original class definition. They each have their advantages and disadvantages because creating extension fields isn’t a naturally supported pattern in Kotlin.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.