Nullable generic types with KClass<T>

Let us say we have a class Data that holds either nullable, or not-null values with generics.

class Data<T>(val data: T)

fun <T> Data<T>.get(): T = data

fun main() {
    val example = Data<String?>("Hello")
    example.get() // Nullable - returns String?
    val example2 = Data("Hello") // Obviously implicitly the same as Data<String>("Hello")
    example2.get() // NotNull - returns String
}

Now this works and is almost exactly what I want (especially Syntax). But I need the type of the KClass too. Which gives us

class Data<T>(val data: T, val kclass: KClass<T>)

That won’t work since T of KClass is not within T: Any.

  • I want the nullability determined by the constructor
  • I want the generic type of KClass
  • Optimally only a single Type Parameter for both KClass<T> and val data: T

So the optimal solution would be to get the non-null type of T with “T!” or some other imaginary thing, but I doubt that is possible.

class Data<T>(val data: T, val kclass: KClass<T!>)

Using multiple generics, one for KClass<T>, the other for val data: T would not be elegant, and I hope to get around that in some way. I do not want to type the generic twice to instantiate a Data object.

I played around with reified, but I just could not come up with something elegant. I know you can use <reified T> in an inline function and get the class of T even if T is <T: Any?> (T::class). Maybe you could make it work with that in some way?

Does anyone know an elegant solution to this?
Thank you in advance!

Probably the closest to your expectations:

class Data<T>(val data: T)
inline fun <reified T : Any, S : T?> Data<S>.kclass(): KClass<T> = T::class

However, this only works with the new type inference algorithm. If will be enabled by default in 1.4, however you can try it out by specifying -XXLanguage:+NewInference compiler argument.

In case you are using older Kotlin version or you would like to only use stable features, there can be a few approaches that may work, depending on your use case.

If you only need to retrieve KClass for non-nullable case:
inline fun <reified T : Any> Data<T>.kclass(): KClass<T> = T::class

If you may use KType instead of KClass:

class Data<T>(val data: T, val type: KType)

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> Data(data: T) = Data(data, typeOf<T>())

And you can actually retrieve KClass instance with classifer property of KType, however you would need to cast it.

Finally, in the case of two type parameters that you mentioned, factory methods can help as well to save some typing. However unfortunately they would need to be named differently, as otherwise they would be considered to have same JVM signature:

class Data<T : Any, S : T?>(val data: S, val kclass: KClass<T>)

inline fun <reified T : Any> Data(data: T) = Data(data, T::class)
inline fun <reified T : Any> OptionalData(data: T?) = Data(data, T::class)
2 Likes