new - Genéricos de Java: ¿por qué es posible esta salida?
new icon java (4)
El comportamiento que muestra IntelliJ es claro para mí:
Tienes un elenco sin
MyClass
en
MyClass
.
Esto significa que el
new Integer(8)
no se convierte inmediatamente en
Long
sino en el
Number
borrado (que funciona), cuando se ejecuta esta línea:
N n =(N)(new Integer(8));
Ahora echemos un vistazo a las declaraciones de salida:
System.out.println(new MyClass<Long>().n);
se reduce a
String.valueOf(new MyClass<Long>().n)
->
((Object)new MyClass<Long>().n).toString()
que funciona bien, porque n se accede a través de
Object
y también el Se accede al método
toString()
a través del tipo estático
Object
-> no se produce
Long
a
Long
.
new MyClass<Long>().n.toString()
fallará con una excepción, porque se intenta acceder a
toString()
a través del tipo estático
Long
.
Por lo tanto, se produce una conversión de n para escribir
Long
que no es posible (
Integer
no se puede convertir a
Long
).
Lo mismo ocurre al ejecutar la segunda declaración:
System.out.println(new MyClass<Long>().n.getClass());
Se
getClass
método
getClass
(declarado en
Object
) de tipo
Long
a través del tipo estático
Long
.
Por lo tanto, se produce una conversión de n para escribir
Long
que produce una excepción de conversión.
Comportamiento de JShell:
Traté de reproducir la excepción resultante para la primera declaración de salida en JShell - Java 9 Early Access Build 151:
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)
Pero parece que JShell da exactamente los mismos resultados que IntelliJ.
System.out.println(new MyClass<Long>().n);
salidas 8 - sin excepción.
Tengo esta clase:
class MyClass<N extends Number> {
N n = (N) (new Integer(8));
}
Y quiero obtener estos resultados:
System.out.println(new MyClass<Long>().n);
System.out.println(new MyClass<Long>().n.getClass());
-
Salida de la primera instrucción
System.out.println()
:8
-
Salida de la segunda instrucción
System.out.println()
:java.lang.ClassCastException: java.lang.Integer (in module: java.base) cannot be cast to java.lang.Long (in module: java.base)
¿Por qué obtengo el primer resultado? ¿No hay un elenco también? ¿Por qué obtengo la excepción en la segunda salida?
PD: yo uso Java 9; Lo probé con JShell y obtuve una excepción en ambas salidas. Luego lo probé con IntelliJ IDE y obtuve el primer resultado, pero la excepción en el segundo.
Esto está sucediendo porque ya ha definido n como objeto de entero, por lo que no lo lanzará demasiado
use Integer en
MyClass
en sysout como
System.out.println(new MyClass<Integer>().n);
o defina
n
como:
N n =(N)(new Long(8));
.
Esto sucede debido a la eliminación de Java.
Como
Integer
extiende
Number
, el compilador acepta la conversión a
N
En tiempo de ejecución, dado que
N
se reemplaza por
Number
(debido a la eliminación), no hay ningún problema para almacenar un número
Integer
dentro de
n
.
El argumento del método
System.out.println
es de tipo
Object
por lo que no hay problema para imprimir el valor de
n
.
Sin embargo, cuando se llama a un método en
n
, el compilador agrega una verificación de tipo para garantizar que se llame al método correcto.
Por lo tanto, resulta en una
ClassCastException
.
Tanto la excepción como la no excepción son comportamientos permitidos. Básicamente, esto se reduce a cómo el compilador borra las declaraciones, ya sea para algo así sin lanzamientos:
System.out.println(new MyClass().n);
System.out.println(new MyClass().n.getClass());
o algo parecido a esto con los moldes:
System.out.println((Long)new MyClass().n);
System.out.println(((Long)new MyClass().n).getClass());
o uno para una declaración y uno para el otro. Ambas versiones son código Java válido que se compilará. La pregunta es si está permitido que el compilador compile en una versión, en la otra, o en ambas.
Es permisible insertar un reparto aquí porque eso es generalmente lo que sucede cuando tomas algo de un contexto genérico donde el tipo es una variable de tipo y lo devuelves a un contexto donde la variable de tipo adquiere un tipo específico.
Por ejemplo, puede asignar el
new MyClass<Long>().n
a una variable de tipo
Long
sin ninguna conversión, o pasar el
new MyClass<Long>().n
a un lugar que espera
Long
sin ninguna conversión, en ambos casos obviamente requerirá que el compilador inserte un molde.
El compilador puede decidir insertar siempre un reparto cuando tiene un
new MyClass<Long>().n
, y no está mal hacerlo, ya que se supone que la expresión tiene el tipo
Long
.
Por otro lado, también es permisible no tener una conversión en estas dos declaraciones, porque en ambos casos la expresión se usa en un contexto en el que se puede usar cualquier
Object
, por lo que no se necesita conversión para hacer que se compile y sea de tipo seguro. .
Además, en ambas declaraciones, un reparto o ningún reparto no supondría ninguna diferencia en el comportamiento si el valor fuera realmente
Long
.
En la primera instrucción, se pasa a la versión de
.println()
que toma
Object
, y no hay más sobrecarga específica de
println
que toma
Long
o
Number
o algo así, por lo que se elegirá la misma sobrecarga independientemente de si El argumento se considera
Long
u
Object
.
Para la segunda instrucción,
.getClass()
es proporcionado por
Object
, por lo que está disponible independientemente de si la cosa de la izquierda es
Long
u
Object
.
Dado que el código borrado es válido tanto con y sin el elenco y el comportamiento sería el mismo con y sin el elenco (suponiendo que la cosa sea realmente
Long
), el compilador podría optar por optimizar el reparto.
Un compilador podría incluso tener un reparto en un caso y no en el otro, tal vez porque solo optimiza el reparto en ciertos casos simples, pero no se molesta en realizar análisis en casos más complicados. No necesitamos pensar en por qué un compilador en particular decidió compilar en un formulario u otro para una declaración en particular, porque ambos son permisibles, y no debe confiar en que funcione de una manera u otra.