Search code examples
kotlinkotlin-extension

Kotlin, implement extension var property


It seems that a simple extension property like the following does not work.

var Dog.age = 0;

What is the recommended way to implement this? I have tried the following, and it worked, but this will prevent any Dog object from cleaned up by the Garbage Collector, won't it?

class Dog
{
}

val dogAgeMap=HashMap<Dog, Int>();
var Dog.age:Int
    get() =  dogAgeMap[this]?: 0;
    set(value){ dogAgeMap[this] = value}

class PetShop
{
    fun work()
    {
        val d1 = Dog();
        d1.age = 100;
        val d2 = Dog();
        d2.age = 200;
        println("${d1.age}, ${d2.age}");
    }
}

fun main(args:Array<String>)
{
    PetShop().work();
}

Solution

  • Correct, this will prevent the Dog instances on which the age setter has been called to be GCed inside the scope of where the dogAgeMap is defined. If you defined the Dog.age extension property (and thus dogAgeMap) in a limited scope with a limited (short) lifespan, then you are okay.

    However, if that is not the case, and you need the age info all across you application, then age should just be part of the original class definition and you don't ever run into this problem.

    Solution in this case

    class Dog(val age: Int)
    

    If you need the age information only in one part of your application, then a better way would be to create the lookup (the HashMap) only for that part, or to simply use an enriched class with age (or a wrapper class with age) instead of the Dog class in that part of your application. And when you are done with work there, you clean up the map or the enriched class instances. In that way no instances will leak.


    But if you really really want to do it with an extension property across the whole application, and thus you need to keep the reference to the dogAgeMap all the time, then you need to take care of leaking memory if you have a lot of instances that you go through and set their age.

    If that is your case you can use a WeakHashMap<Dog, Int> instead. A WeakHashMap only keeps weak references and it won't prevent Dog instances to be GCed (once your strong references are no longer retained).

    import java.util.WeakHashMap
    
    val dogAgeMap = WeakHashMap<Dog, Int>()
    var Dog.age: Int
        get() = dogAgeMap[this] ?: 0
        set(value) {
            dogAgeMap[this] = value
        }
    

    Note however, that WeakHashMap is a Java class and not part of Kotlin core library, so if you use Kotlin for multiplatform, this won't work. In that case you would need a WeakHashMap implementation (library) on each platform.

    An alternative way to do this if your data for dogs also contains an ID for each dog, would be to use the ID as the lookup key instead. That would be possible to port to all platforms. The implementation would then change to

    // I am using a Long here, but it could be whatever type that
    // is small enough to not cause memory concerns, since
    // these keys would still exist in memory because a normal HashMap is used.
    class Dog(val id: Long) {}
    
    val dogAgeMap = HashMap<Long, Int>()
    var Dog.age: Int
        get() = dogAgeMap[id] ?: 0
        set(value) {
            dogAgeMap[id] = value
        }