Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get generic parameter class in Kotlin

Firebase's snapshot.getValue() expects to be called as follows:

snapshot?.getValue(Person::class.java) 

However I would like to substitute Person with a generic parameter that is passed into the class via the class declaration i.e.

class DataQuery<T : IModel>  

and use that generic parameter to do something like this:

snapshot?.getValue(T::class.java) 

but when I attempt that I get an error stating that

only classes can be used on the left-hand side of a class literal

Is it possible to provide a class constraint on the generic parameter like in C# or is there some other syntax I can use to get the type info for the generic param?

like image 717
Jaja Harris Avatar asked Dec 06 '15 20:12

Jaja Harris


People also ask

What is generic class in Kotlin?

Generics means we use a class or an implementation in a very generic manner. For example, the interface List allows us for code reuse. We are able to create a list of Strings, of integer values and we will have the same operations even if we have different types.

How do I call generic method Kotlin?

Kotlin generic example When we call the generic method <T>printValue(list: ArrayList<T>) using printValue(stringList), the type T of method <T>printValue(list: ArrayList<T>)will be replaced by String type.

How do you make a generic object in Kotlin?

An object is a singleton. A singleton is something for which only one instance exists. Type parameters allow to specify type arguments for a specific instance of a class, and as such, make sense only when there is more than one instance. Therefore, no, there is no way to create a generic object in Kotlin.

How do I know my T type in Kotlin?

If you need to check if something is of generic type T you need to to have an instance of Class<T> to check against. This is a common technique in Java however in Kotlin we can make use of an inlined factory method that gets us the class object.


2 Answers

For a class with generic parameter T, you cannot do this because you have no type information for T since the JVM erases the type information. Therefore code like this cannot work:

class Storage<T: Any> {     val snapshot: Snapshot? = ...      fun retrieveSomething(): T? {         return snapshot?.getValue(T::class.java) // ERROR "only classes can be used..."     } } 

But, you can make this work if the type of T is reified and used within an inline function:

class Storage {     val snapshot: Snapshot? = ...      inline fun <reified T: Any> retrieveSomething(): T? {         return snapshot?.getValue(T::class.java)     } } 

Note that the inline function if public can only access public members of the class. But you can have two variants of the function, one that receives a class parameter which is not inline and accesses private internals, and another inline helper function that does the reification from the inferred type parameter:

class Storage {     private val snapshot: Snapshot? = ...      fun <T: Any> retrieveSomething(ofClass: Class<T>): T? {         return snapshot?.getValue(ofClass)     }      inline fun <reified T: Any> retrieveSomething(): T? {         return retrieveSomething(T::class.java)     } } 

You can also use KClass instead of Class so that callers that are Kotlin-only can just use MyClass::class instead of MyClass::class.java

If you want the class to cooperate with the inline method on the generics (meaning that class Storage only stores objects of type T):

class Storage <T: Any> {     val snapshot: Snapshot? = ...      inline fun <reified R: T> retrieveSomething(): R? {         return snapshot?.getValue(R::class.java)     } } 

The link to reified types in inline functions: https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters

like image 61
Jayson Minard Avatar answered Nov 16 '22 04:11

Jayson Minard


What you need is reified modifier for your generic param, you can read about it here. https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters So if you do something like that:

inline fun <reified T : Any>T.logTag() = T::class.java.simpleName 

you will get name of the actual caller class, not "Object".

like image 32
Yaroslav Avatar answered Nov 16 '22 03:11

Yaroslav