J'ai une interface qui contient un tableau (ou une liste) de T et des métadonnées.
interface DataWithMetadata<T> {
val someMetadata: Int
fun getData(): Array<T>
}
Si j'écris l'implémentation la plus simple de l'interface, j'obtiens une erreur de compilation sur emptyArray()
: "Impossible d'utiliser T comme paramètre de type réifié. Utilisez plutôt une classe."
class ArrayWithMetadata<T>(override val someMetadata: Int): DataWithMetadata<T> {
private var myData: Array<T> = emptyArray()
override fun getData(): Array<T> {
return myData
}
fun addData(moreData: Array<T>) {
this.myData += moreData
}
}
Cependant, si je modifie l'interface et l'implémentation en une liste, je n'ai aucun problème de compilation:
interface DataWithMetadata<T> {
val someMetadata: Int
fun getData(): List<T>
}
class ListWithMetadata<T>(override val someMetadata: Int): DataWithMetadata<T> {
private var myData: List<T> = emptyList()
override fun getData(): List<T> {
return myData
}
fun addData(moreData: Array<T>) {
this.myData += moreData
}
}
Je soupçonne que mon numéro contient une leçon intéressante sur les génériques Kotlin. Quelqu'un peut-il me dire ce que le compilateur fait sous le capot et pourquoi Array échoue mais List ne le fait pas? Existe-t-il un moyen idiomatique de rendre la mise en œuvre Array compilée dans ce contexte?
Question bonus: la seule raison pour laquelle j'ai choisi Array Over List, c'est que les développeurs de Kotlin préfèrent souvent les tableaux. Est-ce le cas et si oui, pourquoi?
En examinant la déclaration de emptyArray()
dans kotlin stdlib (jvm), nous remarquons le paramètre de type reified
:
public inline fun <reified @PureReifiable T> emptyArray(): Array<T>
Le paramètre type reified
signifie que vous avez accès à la classe T
au moment de la compilation et que vous pouvez y accéder comme T::class
. Vous pouvez en savoir plus sur les paramètres de type reified
dans le référence Kotlin . Puisque Array<T>
est compilé en Java T[]
, nous devons connaître le type au moment de la compilation, d’où le paramètre reified
. Si vous essayez d'écrire une fonction emptyArray () sans le mot clé reified
, vous obtiendrez une erreur de compilation:
fun <T> emptyArray() : Array<T> = Array(0, { throw Exception() })
Impossible d'utiliser T comme paramètre de type réifié. Utilisez une classe à la place.
Regardons maintenant l'implémentation de emptyList()
:
public fun <T> emptyList(): List<T> = EmptyList
Cette implémentation n'a pas du tout besoin du paramètre T
. Il ne fait que renvoyer l'objet interne EmptyList
, qui hérite lui-même de List<Nothing>
. Le type kotlin Nothing
est le type de retour du mot clé throw
et correspond à une valeur qui n'existe jamais ( reference ). Si une méthode retourne Nothing
, cela revient à lancer une exception à cet endroit. Nous pouvons donc utiliser Nothing
en toute sécurité ici car à chaque fois que nous appellerions EmptyList.get()
, le compilateur sait que cela renverra une exception.
Question bonus:
Venant de Java et de C++, je suis habitué à ArrayList
ou std::vector
pour être beaucoup plus facile à utiliser que les tableaux. J'utilise kotlin maintenant depuis quelques mois et d'habitude, je ne vois pas de grande différence entre les tableaux et les listes lors de l'écriture du code source. Les deux ont des tonnes de fonctions d'extension utiles qui se comportent de la même manière. Cependant, le compilateur Kotlin gère des tableaux et des listes très différentes, car l’interopérabilité Java est très importante pour l’équipe Kotlin. Je préfère généralement utiliser des listes, et c'est ce que je recommanderais également dans votre cas.
Le problème est que le type générique d'une Array
doit être connu à compile time, ce qui est indiqué par le paramètre de type reified
ici, comme indiqué dans la déclaration:
public inline fun <reified @PureReifiable T> emptyArray(): Array<T>
Il est seulement possible de créer des tableaux concrets comme Array<String>
ou Array<Int>
mais pas de type Array<T>
.
Dans cette réponse , vous pouvez trouver plusieurs solutions de contournement. J'espère que vous trouverez un moyen approprié.
La solution qui fonctionnait le mieux pour moi était la suivante:
@Suppress("UNCHECKED_CAST")
var pool: Array<T?> = arrayOfNulls<Any?>(initialCapacity) as Array<T?>