web-dev-qa-db-fra.com

Génériques Java: pourquoi cette sortie est-elle possible?

J'ai cette classe:

class MyClass<N extends Number> {
    N n = (N) (new Integer(8));
}

Et je veux obtenir ces sorties:

System.out.println(new MyClass<Long>().n);
System.out.println(new MyClass<Long>().n.getClass());
  1. Sortie de la première instruction System.out.println():

    8
    
  2. Sortie de la deuxième instruction System.out.println():

    Java.lang.ClassCastException: Java.lang.Integer (in module: Java.base)
        cannot be cast to Java.lang.Long (in module: Java.base)
    

Pourquoi est-ce que je reçois la première sortie? N'y a-t-il pas aussi un casting? Pourquoi ai-je l'exception dans la deuxième sortie?

PS: J'utilise Java 9; Je l'ai essayé avec le JShell et j'ai eu une exception sur les deux sorties. Ensuite, j'ai essayé avec IntelliJ IDE et obtenu la première sortie mais l'exception à la seconde.

51
Socke

Le comportement montré par IntelliJ est clair pour moi:

Vous avez une distribution non contrôlée dans MyClass. Cela signifie que new Integer(8) n'est pas immédiatement converti en Long mais à l'effacement Number (qui fonctionne), lorsque cette ligne est exécutée: N n =(N)(new Integer(8));

Regardons maintenant les instructions de sortie:

System.out.println(new MyClass<Long>().n);

cela se résume à String.valueOf(new MyClass<Long>().n) -> ((Object)new MyClass<Long>().n).toString() qui fonctionne correctement, car n est accessible via Object et la méthode toString() est également accessible via le type statique Object -> aucun transtypage en Long se produit. new MyClass<Long>().n.toString() échouerait avec une exception, car toString() est essayé avec un type statique Long. Par conséquent, une conversion de n dans le type Longoccurs n'est pas possible (Integer ne peut pas être convertie en Long).

La même chose se produit lors de l'exécution de la 2ème instruction:

System.out.println(new MyClass<Long>().n.getClass()); 

La méthode getClass (déclarée dans Object) de type Long est essayée par le type statique Long. Par conséquent, une conversion de n vers le type Long se produit, générant une exception de distribution.

JComportement de l'enveloppe:

J'ai essayé de reproduire l'exception résultante pour la première instruction de sortie sur JShell - Accès anticipé à la version 9 de Java 9:

jshell> class MyClass<N extends Number> {
   ...>     N n = (N) (new Integer(8));
   ...> }
|  Warning:
|  unchecked cast
|    required: N
|    found:    Java.lang.Integer
|      N n = (N) (new Integer(8));
|                ^--------------^
|  created class MyClass

jshell> System.out.println(new MyClass<Long>().n);
8

jshell> System.out.println(new MyClass<Long>().n.getClass());
|  Java.lang.ClassCastException thrown: Java.base/Java.lang.Integer cannot be cast to Java.base/Java.lang.Long
|        at (#4:1)

Mais il semble que JShell donne exactement les mêmes résultats que IntelliJ. System.out.println(new MyClass<Long>().n); sort 8 - pas d'exception.

39
Calculator

Cela se produit à cause de l'effacement de Java.

Puisque Integer étend Number, le compilateur accepte la conversion en N. Au moment de l'exécution, puisque N est remplacé par Number (en raison de l'effacement), il n'y a aucun problème pour stocker une Integer dans n.

L'argument de la méthode System.out.println est de type Object et il n'y a donc aucun problème pour imprimer la valeur de n.

Cependant, lors de l'appel d'une méthode sur n, une vérification de type est ajoutée par le compilateur pour garantir que la bonne méthode sera appelée. Il en résulte une ClassCastException.

25
Raphaël

Exception et aucune exception sont des comportements autorisés. En gros, cela revient à la façon dont le compilateur efface les instructions, que ce soit à quelque chose comme ceci sans cast:

System.out.println(new MyClass().n);
System.out.println(new MyClass().n.getClass());

ou à quelque chose comme ça avec des moulages:

System.out.println((Long)new MyClass().n);
System.out.println(((Long)new MyClass().n).getClass());

ou un pour une déclaration et un pour l'autre. Les deux versions sont du code Java valide qui sera compilé. La question est de savoir s'il est permis au compilateur de compiler une version, ou l'autre, ou les deux.

Il est permis d'insérer une distribution ici car c'est généralement ce qui se produit lorsque vous prenez quelque chose dans un contexte générique où le type est une variable de type et que vous le retournez dans un contexte où la variable de type prend un type spécifique. Par exemple, vous pouvez affecter new MyClass<Long>().n à une variable de type Long sans transtypes, ou transmettre new MyClass<Long>().n à un emplacement attendu par Long sans transtypage, les deux cas nécessitant évidemment que le compilateur insère un transtypage. Le compilateur peut simplement décider de toujours insérer un transtypage lorsque vous avez new MyClass<Long>().n. Ce n'est pas faux de le faire, car l'expression est supposée avoir le type Long.

D'un autre côté, il est également possible de ne pas transtyper ces deux instructions car, dans les deux cas, l'expression est utilisée dans un contexte où aucune variable Object ne peut être utilisée, aucune conversion n'est donc nécessaire pour la compiler et la sécuriser. . De plus, dans les deux déclarations, une conversion ou aucune ne ferait aucune différence de comportement si la valeur était bien une Long. Dans la première instruction, il est passé à la version de .println() qui prend Object et il n'y a plus de surcharge spécifique de println qui prend Long ou Number ou quoi que ce soit d'autre. La même surcharge sera donc choisie, que l'argument soit considéré ou non. comme Long ou Object. Pour la deuxième déclaration, .getClass() est fourni par Object. Il est donc disponible, que l'élément affiché à gauche soit un Long ou un Object. Étant donné que le code effacé est valide avec et sans la distribution et que le comportement serait identique avec et sans la distribution (en supposant que la chose est bien une Long), le compilateur pourrait choisir d'optimiser la conversion.

Un compilateur pourrait même avoir une distribution dans un cas et pas dans l'autre, peut-être parce qu'il optimise la distribution dans certains cas simples, mais ne se donne pas la peine d'effectuer une analyse dans des cas plus complexes. Nous n'avons pas besoin de nous attarder sur les raisons pour lesquelles un compilateur a décidé de compiler une forme ou une autre pour une déclaration donnée, car les deux sont autorisées, et vous ne devez pas compter sur elle pour fonctionner d'une manière ou d'une autre.

1
newacct

Cela se produit parce que vous avez déjà défini n comme objet de nombre entier pour qu’il ne soit pas converti trop longtemps. 

soit utiliser Integer dans la MyClass dans le sysout comme

System.out.println(new MyClass<Integer>().n);

ou définissez n comme: N n =(N)(new Long(8));.

0
Aman Goyal