is there a good way to associate something with a ...
# getting-started
l
is there a good way to associate something with a type in a way it can be generically looked up, but without each instance of the type holding a copy ? e.g. consider an abstract parent class
Animal
and some inheriting classes
Dog
Cat
etc, and i want each type of animal to have a
lifeExpectancy
. now of course i could just create
abstract val lifeExpectancy: Float
inside
Animal
and have each child class override that, but that would result in every instance of
Dog
holding a copy of the same number, which seems a bit redundant. (yes i know the overhead in this example is insignificant, but the problem is scalable). alternatively i could put the lifeExpectancy in the companion object, but then i can't generically look it up as the companion object can't be accessed generically (or at least i wouldn't know how to do that). i could store a reference to the companion object (or just the data i want to associate with the type in case it's not a primitive) in each instance to keep the overhead insignificantly low, but that would be incredibly ugly is there any way to do this more elegantly?
c
Maybe just provide that value from a function or custom getter? You can define that inside the base class, but the class itself wouldn't then hold onto the value in memory, but it's just created when needed.
Copy code
abstract class Animal {
    abstract val lifeExpectancy: Float
}
class Dog : Animal() {
    override val lifeExpectancy: Float get() = LIFE_EXPECTANCY

    companion object {
        const val LIFE_EXPECTANCY = 9.5f
    }
}
Alternatively, you could use Kotlin reflection to access the companion object generically, though there's no real way to force a given class to have the proper members in its companion. You could create an interface that the companion object implements to help, but it would still fall to convention to actually be done properly
Copy code
fun main() {
    val dog = Dog()
    val dogClass = dog::class

    val companionForDog = dogClass.companionObjectInstance as AnimalAttributes
    val dogLifeExpectancy = companionForDog.lifeExpectancy 
}
interface AnimalAttributes {
    abstract val lifeExpectancy: Float
}
class Dog {
    companion object : AnimalAttributes {
        override val lifeExpectancy: Float get() = 9.5f
    }
}
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect.full/companion-object-instance.html
And know that the kotlin.reflect dependency is huge, several megabytes, so isn't something to take on lightly if binary size is a concern (such as an Android app)
l
using reflection feels kinda ugly too tbh
t
Maybe you need an object that represents the species of the Animal
1
and every Dog might have the same instance of that object
especially if you scale this up, I could see that being valuable
y
how about this (playground):
Copy code
import kotlin.reflect.*
abstract class Animal(lifeExpectancy: Float) {
    init {
        recordLifeExpectancy(this::class, lifeExpectancy)
    }
    
    val lifeExpectancy: Float get() = lifeExpectancyByType[this::class]!!
    companion object {
        val lifeExpectancyByType: Map<KClass<*>, Float> get() = _lifeExpectancyByType
        private val _lifeExpectancyByType: MutableMap<KClass<*>, Float> = mutableMapOf()
        fun recordLifeExpectancy(type: KClass<*>, lifeExpectancy: Float) {
            _lifeExpectancyByType.getOrPut(type) { lifeExpectancy }
        }
    }
}

class Dog: Animal(12f)
class Cat: Animal(42f)

fun main() {
    println(Dog().lifeExpectancy)
    println(Cat().lifeExpectancy)
    println(Dog().lifeExpectancy)
    println(Cat().lifeExpectancy)
    println(Dog().lifeExpectancy)
    println(Cat().lifeExpectancy)
    println(Animal.lifeExpectancyByType)
}
However, keep in mind that the problem only scales if you have millions of
Dog
and
Cat
objects, and that each Dog object having the same value in inside of it is fine since if it's an object only a reference ("pointer") to that object will be stored (according to how the JVM works)