Kotlin: Extension fields in Kotlin

Kotlin Programming

Question or issue of Kotlin Programming:

It’s easy to write extension methods in Kotlin:

class A { }
class B {
    fun A.newFunction() { ... }
}

But is there some way to create extension variable? Like:

class B {
    var A.someCounter: Int = 0
}

How to solve this issue?

Solution no. 1:

No – the documentation explains this:


Extensions do not actually modify classes they extend. By defining an extension, you do not insert new members into a class, but merely make new functions callable with the dot-notation on instances of this class.

and


Note that, since extensions do not actually insert members into classes, there’s no efficient way for an extension property to have a backing field. This is why initializers are not allowed for extension properties. Their behavior can only be defined by explicitly providing getters/setters.

Thinking about extension functions/properties as just syntactic sugar for calling a static function and passing in a value hopefully makes this clear.

However, if you really, really want to do something like this…

As stated above regarding efficiency, an additional backing field added directly to the class is the best way to store data non-derivable from existing non-private members from the class. However, if you don’t control the implementation of the class and are dead-set on creating a new property that can store new data, it can be done in a way that is not abysmally inefficient by using separate external tables. Use a separate map that keys on object instances of this class with values that map directly to the value you want to add then define an extension getter and/or setter for this property which uses your external table to store the data associated with each instance.

val externalMap = mutableMapOf()

var ExistingClass.newExtensionProperty : Int
    get() = externalMap[this] ?: 0
    set(value:Int) { externalMap[this] = value }

The additional map lookups will cost you – and you need to consider memory leaks, or using appropriately GC-aware types, but it does work.

Solution no. 2:

You can create an extension property with overridden getter and setter:

var A.someProperty: Int
  get() = /* return something */
  set(value) { /* do something */ }

But you cannot create an extension property with a backing field because you cannot add a field to an existing class.

Solution no. 3:

There’s no way to add extension properties with backing fields to classes, because extensions do not actually modify a class.

You can only define an extension property with custom getter (and setter for var) or a delegated property.


However, if you need to define an extension property which would behave as if it had a backing field, delegated properties come in handy.
The idea is to create a property delegate that would store the object-to-value mapping:

  • using the identity, not equals()/hashCode(), to actually store values for each object, like IdentityHashMap does;

  • not preventing the key objects from being garbage collected (using weak references), like WeakHashMap does.

Unfortunately, there is no WeakIdentityHashMap in JDK, so you have to implement your own (or take a complete implementation).

Then, based on this mapping you can create a delegate class satisfying the property delegates requirements. Here’s an example non-thread-safe implementation:

class FieldProperty(
    val initializer: (R) -> T = { throw IllegalStateException("Not initialized.") }
) {    
    private val map = WeakIdentityHashMap()

    operator fun getValue(thisRef: R, property: KProperty<*>): T =
            map[thisRef] ?: setValue(thisRef, property, initializer(thisRef))

    operator fun setValue(thisRef: R, property: KProperty<*>, value: T): T {
        map[thisRef] = value
        return value
    }
}

Usage example:

var Int.tag: String by FieldProperty { "$it" }

fun main(args: Array) {
    val x = 0
    println(x.tag) // 0

    val z = 1
    println(z.tag) // 1
    x.tag = "my tag"
    z.tag = x.tag
    println(z.tag) // my tag
}

When defined inside a class, the mapping can be stored independently for instances of the class or in a shared delegate object:

private val bATag = FieldProperty { "$it" }

class B() {
    var A.someCounter: Int by FieldProperty { 0 } // independent for each instance of B
    var A.tag: String by bATag // shared between the instances, but usable only inside B
}

Also, please note that identity is not guaranteed for Java’s primitive types due to boxing.

And I suspect the performance of this solution to be significantly worse than that of regular fields, most probably close to normal Map, but that needs further testing.

For nullable properties support and thread-safe implementation please refer to here.

Solution no. 4:

You can’t add a field, but you can add a property, that delegates to other properties/methods of the object to implement its accessor(s). For example suppose you want to add a secondsSinceEpoch property to the java.util.Date class, you can write

var Date.secondsSinceEpoch: Long 
    get() = this.time / 1000
    set(value) {
        this.time = value * 1000
    }

Solution no. 5:

If you are extending View you can do it quite easily like this…
This is example how I create some my custom class Event property in EditText class extension:

Define id for key :



    

Define one reusable extension like this:

fun  View.tagProperty(@IdRes key: Int, onCreate: () -> T): T {
    @Suppress("UNCHECKED_CAST")
    var value = getTag(key) as? T
    if (value.isNull) {
        value = onCreate()
        setTag(key, value)
    }
    return value!!
}

Use it in wherever View extension you need:

val EditText.eventClear get() = tagProperty(R.id.EditTextEventOnClearTagKey) { event() }

Hope this helps!