La réponse d'Alin détaille parfaitement pourquoi le générique n'est pas disponible au moment de l'exécution. Vous pouvez vous rapprocher un peu plus de ce que vous voulez avec la magie de ClassTag
mais vous devez toujours être conscient de certains problèmes liés aux génériques Java.
import scala.reflect.ClassTag
var l = List("Some string", 3)
def containsType[T](implicit cls: ClassTag[T]): Boolean = {
l.exists(cls.runtimeClass.isInstance(_))
}
Maintenant, chaque fois que vous appelez containsType
un argument supplémentaire caché de type ClassTag[T]
le dépasse. Donc quand vous écrivez, par exemple, println(containsType[String])
puis elle est compilée en
scala.this.Predef.println($anon.this.containsType[String](ClassTag.apply[String](classOf[java.lang.String])))
Un argument supplémentaire est passé à containsType
a saber ClassTag.apply[String](classOf[java.lang.String])
. C'est une façon très longue de faire passer explicitement un Class<String>
ce que vous devriez faire manuellement en Java. Et java.lang.Class
a un isInstance
función.
La plupart du temps, cela fonctionne, mais il y a encore des problèmes majeurs. Génériques arguments sont complètement effacées au moment de l'exécution, ce qui ne vous aidera pas à distinguer entre une Option[Int]
et un Option[String]
dans votre liste, par exemple. En ce qui concerne la JVM, ils sont tous deux Option
.
Deuxièmement, Java a une histoire malheureuse avec les types primitifs, donc containsType[Int]
sera en fait fausse dans votre cas, malgré le fait que la 3
dans votre liste est en fait un Int
. C'est parce que, en Java, les génériques peuvent seulement être classe des types, et non des primitives, de sorte qu'un List
ne peut jamais contenir int
(notez le "i" minuscule, car il s'agit d'un élément fondamentalement différent d'une classe en Java).
Scala recouvre un grand nombre de ces détails de bas niveau, mais les fissures apparaissent dans des situations comme celle-ci. Scala voit que vous construisez une liste de String
et Int
il veut donc construire une liste du supertype commun des deux, qui est Any
(les chaînes et les ints n'ont pas de super-type commun plus spécifique que Any
). Au moment de l'exécution, Scala Int
peut se traduire soit par int
(le primitif) ou Integer
(l'objet). Scala favorisera le premier pour des raisons d'efficacité, mais lorsqu'il stocke dans des conteneurs génériques, il ne peut pas utiliser un type primitif. Ainsi, alors que Scala pense que votre liste l
contient un String
et un Int
, Java pense qu'il contient un String
y un java.lang.Integer
. Et pour rendre les choses encore plus folles, les deux int
y java.lang.Integer
ont distinct Class
instances.
Alors summon[ClassTag[Int]]
en Scala est java.lang.Integer.TYPE
qui est un Class<Integer>
instance représentant le type primitif int
(oui, le type de non-classe int
a un Class
instance qui le représente). Alors que summon[ClassTag[java.lang.Integer]]
es java.lang.Integer::class
une personne distincte Class<Integer>
représentant le type non primitif Integer
. Et au moment de l'exécution, votre liste contient ce dernier.
En résumé, les génériques en Java sont un véritable gâchis. Scala fait de son mieux pour travailler avec ce qu'il a, mais lorsque vous commencez à jouer avec la réflexion (qui ClassTag
), vous devez commencer à réfléchir à ces problèmes.
println(containsType[Boolean]) // false
println(containsType[Double]) // false
println(containsType[Int]) // false (list can't contain primitive type)
println(containsType[Integer]) // true (3 is converted to an Integer)
println(containsType[String]) // true (class type so it works the way you expect)
println(containsType[Unit]) // false
println(containsType[Long]) // false