How to get class of generic type parameter in Kotl

2020-07-11 08:46发布

问题:

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)
}

}

回答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>.



回答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!