Home > Enterprise >  Kotlin reified type at class level
Kotlin reified type at class level

Time:03-30

I am trying to inherit a library API for enums.

abstract class SomeLibraryClass<T> {
    abstract fun test(key: String): T
}

class Item<T : Enum<T>> : SomeLibraryClass<T>() {
    override fun test(key: String): T {
        return enumValueOf(key)
    }
}

Can't make T as reified or test as inline. How can I inherit the library class for enum?

CodePudding user response:

This is not possible. Reified type has to be known at the compile time. This is accomplished by inlining the body of the function into the call site, where T is known.

Parameterized class doesn't know its T, so T can't be used as a reified type.

The solution is to capture an instance of Class<T> or KClass<T> when initializing Item and keep it in a property:

class Item<T : Enum<T>>(
    private val enumClass: KClass<T>
) : SomeLibraryClass<T>() {
    override fun test(key: String): T {
        return java.lang.Enum.valueOf(enumClass.java, key)
    }
}

Even better, if we don't plan to extend Item, then we can make the constructor internal and provide a reified factory function that captures KClass/Class:

class Item<T : Enum<T>> @PublishedApi internal constructor(
    private val enumClass: KClass<T>
) : SomeLibraryClass<T>() {
    companion object {
        inline fun <reified T : Enum<T>> create() = Item(T::class)
    }

    override fun test(key: String): T {
        return java.lang.Enum.valueOf(enumClass.java, key)
    }
}

We use it like this:

val item = Item.create<Color>()
println(item.test("RED"))

java.lang.Enum.valueOf is far from ideal, but AFAIK unfortunately Kotlin does not provide a multiplatform and smooth way to acquire enum values from KClass. This feature was requested years ago: https://youtrack.jetbrains.com/issue/KT-14743 , but still wasn't implemented.

CodePudding user response:

The type parameter T does not exist at run-time which means that you cannot use it as reified type parameter for an inline function. You need to have the actual type as an additional parameter in order to use it, like this:

open class Item<T : Enum<T>>(
    private val type: KClass<T>,
) : SomeLibraryClass<T>() {
    override fun test(key: String): T {
        return type.java.enumConstants.find { it.name == key }
            ?: throw IllegalStateException("element with key $key not in enum")
    }
}

Then you can override that class like this:

class MyItem : Item<MyEnum>(MyEnum::class)

CodePudding user response:

As others have suggested, this is not possible. Reified type parameters are currently only available on function type parameters. There is a reason why reified needs inline to work. (See also my answer here) There are also inline classes (value class) though. They impose many other restrictions about your class, and it would certainly not be applicable in your case, but I just want to mention this to say that the language could be designed to allow something like this to happen:

value class Item<reified T>(val wrapped: SomeLibraryClass<T>) {
     inline fun test(key: String) = enumValueOf(key)
}

That is, if both the class and function are inlined, then it is possible to know, inside the function, what T is. Also note that you can't use inheritance, as that would prevent inlining of the class.

But Kotlin doesn't support this right now, as I'd imagine implementing this would make the compiler much more complicated, and the use case of this is pretty limited.

In any case, you can store a KClass and use reflection:

class Item<T : Enum<T>>(private val enumClass: KClass<T>) : SomeLibraryClass<T>() {
    @Suppress("UNCHECKED_CAST")
    override fun test(key: String) =
        enumClass.staticFunctions.find { it.name == "valueOf" }?.call(key) as T
}

Do note that this is pretty slow.

  • Related