How to get class of generic type parameter in Kotlin

15,429

Solution 1

You can only get the class on reified variables. The same thing happens in java, but with a slightly different message:

public <T> void x(){
    T t = T.class.newInstance();
}

In Java, you'd solve this like:

public <T> void x(Class<T> cls){
    T t = cls.newInstance();
}

The same applies to Kotlin, and any calls. You'd need to get a class instance in most cases. However, Kotlin supports reified generics using a keyword, but only on inline generic functions. You could pass a class, but in functions, it's really easy just using the reified keyword.

As in you can't declare a class with reified generics, which means this is invalid:

class SomeClass<reified T>

But it is valid for inline functions, meaning you can do:

inline fun <reified T> someFunction()

So you have two options. But since you extend a listener, the first option of adding the generics to the function isn't an option. You can't override a non-generic method with generics. It won't compile.

Which leaves the second option, which unfortunately is rather hackish; passing the class to the constructor. So it should look like this:

class FirebaseDBRepo<T : Any>(val child: String, private val cls: Class<T>) {

Now, I don't use Firebase, so I have no clue what classes you'd pass, so for this next example, I just use String.

Kotlin supports some type minimization without going over to raw types. This:

val t = FirebaseDBRepo<String>("", String::class.java)

Could be shortened to this:

val t = FirebaseDBRepo("", String::class.java)

The inferred type in both cases is FirebaseDBRepo<String>.

Solution 2

Since you are running on the JVM, type erasure is a thing. This means (in simplified terms), that during compilation, the generics are simply ignored. Therefore, you cannot get the class of T, as the JVM doesn't even know what you mean by "T".

Kotlin uses a clever trick to come around this limitation in some cases. When you are using inline functions, the compiler does not call the function you defined, but instead, copies the whole body to the location where you called it. This can only be done for inline functions. Not classes.

There is a workaround tough: Just add private val classT: Class<T> to the constructor and use the parameter instead!

Share:
15,429
Ricardo
Author by

Ricardo

Ricardo :)

Updated on July 22, 2022

Comments

  • Ricardo
    Ricardo almost 2 years

    I would like get the class property from a generic type T. I've decided to extend to Any but I'm getting an error. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/index.html#extension-properties

    I have the following code:

    class FirebaseDBRepo<T : Any>(val child:String) {
    
    private var callback: FirebaseDatabaseRepositoryCallback<T>? = null
    private val ref: DatabaseReference
    private val listener = object : ValueEventListener {
        override fun onDataChange(dataSnapshot: DataSnapshot) {
    
            //T::class.java is showing the error cannot use t as reified type parameter use class instead
    
            val gameDS = dataSnapshot.getValue(T::class.java)
            callback!!.onSuccess(gameDS!!)
        }
    
        override fun onCancelled(databaseError: DatabaseError) {
    
        }
    }
    
    init {
        ref = FirebaseDatabase.getInstance().reference.child(child)
    }
    
    
    fun addListener(callback: FirebaseDatabaseRepositoryCallback<T>) {
        this.callback = callback
        ref.addValueEventListener(listener)
    }
    
    fun removeListener() {
        ref.removeEventListener(listener)
    }
    
    }
    
  • Drew Nutter
    Drew Nutter about 4 years
    I would love it if Kotlin could figure out what to set cls to if I used val t = FirebaseDBRepo<String>(""). Especially because sometimes I have a class definition that's more like class FirebaseDBRepo<T : Any>(val child: T, private val cls: Class<T>). Kotlin can figure out T from child. In that situation I want to be able to do FirebaseDBRepo("").