web-dev-qa-db-fra.com

Comment obtenir une classe de paramètre générique dans Kotlin

snapshot.getValue() de Firebase s'attend à être appelé comme suit:

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

Cependant, je voudrais remplacer Person par un paramètre générique qui est transmis à la classe via la déclaration de classe i.e. 

class DataQuery<T : IModel> 

et utilisez ce paramètre générique pour faire quelque chose comme ceci:

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

mais quand je tente d'obtenir une erreur indiquant que 

seules les classes peuvent être utilisées à gauche d'un littéral de classe

Est-il possible de fournir une contrainte de classe sur le paramètre générique comme en C # ou existe-t-il une autre syntaxe que je peux utiliser pour obtenir les informations de type du paramètre générique?

30
Jaja Harris

Pour une classe avec le paramètre générique T, vous ne pouvez pas le faire car vous ne disposez d'aucune information de type pour T car la machine virtuelle Java efface les informations de type. Par conséquent, un code comme celui-ci ne peut pas fonctionner:

class Storage<T: Any> {
    val snapshot: Snapshot? = ...

    fun retrieveSomething(): T? {
        return snapshot?.getValue(T::class.Java) // ERROR "only classes can be used..."
    }
}

Mais vous pouvez faire ce travail si le type de T est réifié et utilisé dans une fonction inline:

class Storage {
    val snapshot: Snapshot? = ...

    inline fun <reified T: Any> retrieveSomething(): T? {
        return snapshot?.getValue(T::class.Java)
    }
}

Notez que la fonction inline si public ne peut accéder qu'aux membres publics de la classe. Mais vous pouvez avoir deux variantes de la fonction, une qui reçoit un paramètre de classe qui n'est pas en ligne et accède aux internes privés, et une autre fonction d'assistance en ligne qui effectue la réification à partir du paramètre de type inféré:

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

Vous pouvez également utiliser KClass au lieu de Class pour que les appelants qui utilisent uniquement Kotlin puissent simplement utiliser MyClass::class au lieu de MyClass::class.Java.

Si vous souhaitez que la classe coopère avec la méthode inline sur les génériques (ce qui signifie que la classe Storage ne stocke que les objets de type T):

class Storage <T: Any> {
    val snapshot: Snapshot? = ...

    inline fun <reified R: T> retrieveSomething(): R? {
        return snapshot?.getValue(R::class.Java)
    }
}

Le lien vers les types réifiés dans les fonctions en ligne: https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters

28
Jayson Minard

Ce dont vous avez besoin est un modificateur reified pour votre paramètre générique, vous pouvez en savoir plus ici . https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters . Alors, si vous faites quelque chose comme ça:

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

vous obtiendrez le nom de la classe de l'appelant, et non "Object".

18
Yaroslav

vous pouvez obtenir son type de classe comme celui-ci

snapshot?.getValue((this.javaClass
                        .genericSuperclass as ParameterizedType)
                        .actualTypeArguments[0] as Class<T>)
0
merdan