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());
Sortie de la première instruction System.out.println()
:
8
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.
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 Long
occurs 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.
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
.
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.
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));
.