2 votes

Dysfonctionnement du paramètre de type

Je suis nouveau dans l'utilisation de Scala et j'essaie de voir si une liste contient des objets d'un certain type.

Lorsque je crée une méthode pour ce faire, j'obtiens les résultats suivants :

var l = List("Some string", 3)
def containsType[T] = l.exists(_.isInstanceOf[T])
containsType[Boolean]   // val res0: Boolean = true
l.exists(_.isInstanceOf[Boolean])   // val res1: Boolean = false

Quelqu'un pourrait-il m'aider à comprendre pourquoi ma méthode ne renvoie pas les mêmes résultats que l'expression de la dernière ligne ?

Merci, Johan

3voto

Silvio Mayolo Points 13050

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

2voto

Alin Gabriel Arhip Points 701

Scala utilise le modèle d'effacement de type des génériques. Cela signifie qu'aucune information sur les arguments de type n'est conservée à l'exécution, et qu'il n'y a donc aucun moyen de déterminer à l'exécution les arguments de type spécifiques de l'objet List objet. Tout ce que le système peut faire est de déterminer qu'un élément d'un List est un paramètre de type arbitraire. Vous pouvez vérifier ce comportement en essayant également d'autres types :

  var l = List("Some string", 3)

  def containsType[T]: Boolean = {
    l.exists(_.isInstanceOf[T])
  }

  println(containsType[Boolean])
  println(containsType[Double])
  println(containsType[Unit])
  println(containsType[Long])

Sortie :

true
true
true
true

Une autre façon de le dire est que isInstanceOf est une fonction synthétique - une fonction générée par le compilateur au moment de la compilation et qui ne peut pas avoir d'arguments de type générique tels que T (car lors de la compilation, il faudrait traduire en instanceof T qui n'est pas un type réel et ne compilerait pas, donc le compilateur le convertit en instanceof Object c'est pourquoi, quel que soit le type que vous donnez à la fonction polymorphe containsType il en résultera toujours true car les appels à cette fonction sont équivalents à containsType[_] du point de vue de la JVM - ce qui est un substitut de Any type.

Les fonctions polymorphes ne peuvent être appelées qu'avec des arguments de type spécifiques (concrets) comme Boolean , Double , String etc. C'est pourquoi :

  println(l.exists(_.isInstanceOf[Boolean]))

Donne :

true

Pour vous avertir d'un comportement d'exécution éventuellement non intuitif, le compilateur émet généralement des avertissements non vérifiés. Par exemple, si vous aviez exécuté votre code dans le REPL de Scala, vous auriez reçu ceci : enter image description here

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X