una - ¿Cómo elige el compilador de Java el tipo de tiempo de ejecución para un tipo parametrizado con múltiples límites?
reloj alarma java (2)
Me gustaría entender mejor qué sucede cuando el compilador de Java encuentra una llamada a un método como el que se muestra a continuación.
<T extends AutoCloseable & Cloneable>
void printType(T... args) {
System.out.println(args.getClass().getComponentType().getSimpleName());
}
// printType() prints "AutoCloseable"
Para mí está claro que no hay ningún tipo <T extends AutoCloseable & Cloneable>
en tiempo de ejecución, por lo que el compilador hace lo menos incorrecto que puede hacer y crea una matriz con el tipo de una de las dos interfaces de delimitación, descartando la otra .
De todos modos, si se cambia el orden de las interfaces, el resultado sigue siendo el mismo.
<T extends Cloneable & AutoCloseable>
void printType(T... args) {
System.out.println(args.getClass().getComponentType().getSimpleName());
}
// printType() prints "AutoCloseable"
Esto me llevó a investigar un poco más y ver qué sucede cuando cambian las interfaces. Me parece que el compilador utiliza algún tipo de regla de orden estricta para decidir qué interfaz es la más importante , y el orden en que aparecen las interfaces en el código no desempeña ningún papel.
<T extends AutoCloseable & Runnable> // "AutoCloseable"
<T extends Runnable & AutoCloseable> // "AutoCloseable"
<T extends AutoCloseable & Serializable> // "Serializable"
<T extends Serializable & AutoCloseable> // "Serializable"
<T extends SafeVarargs & Serializable> // "SafeVarargs"
<T extends Serializable & SafeVarargs> // "SafeVarargs"
<T extends Channel & SafeVarargs> // "Channel"
<T extends SafeVarargs & Channel> // "Channel"
<T extends AutoCloseable & Channel & Cloneable & SafeVarargs> // "Channel"
Pregunta: ¿Cómo determina el compilador de Java el tipo de componente de una matriz varargs de un tipo parametrizado cuando hay varios límites?
Ni siquiera estoy seguro de si el JLS dice algo sobre esto, y ninguna de la información que encontré en Google cubre este tema en particular.
Esta es una pregunta muy interesante. La parte relevante de la especificación es §15.12.4.2. Evaluar Argumentos :
Si el método que se invoca es un método de aridad variable
m
, necesariamente tiene n > 0 parámetros formales. El parámetro formal final dem
necesariamente tiene el tipoT[]
para algunosT
, ym
se invoca necesariamente con k ≥ 0 expresiones de argumentos reales.Si
m
se invoca con k n n expresiones de argumento reales, o sim
se invoca con k = n expresiones de argumento real y el tipo de k ''th expresión de argumento no es compatible conT[]
, entonces la lista de argumentos (e 1
, ...,e n-1
,e n
, ...,e k
) se evalúa como si estuviera escrito como (e 1
, ...,e n-1
,new
|T[]
|{
e n
, ...,e k
}
), donde |T[]
| denota el borrado (§4.6) deT[]
.
Es curiosamente vago acerca de lo que en realidad es "algo de T
". La solución más simple y directa sería el tipo de parámetro declarado del método invocado; eso sería compatible con la asignación y no hay ninguna ventaja real de usar un tipo diferente. Pero, como sabemos, javac
no toma esa ruta y utiliza algún tipo de tipo base común de todos los argumentos o selecciona algunos de los límites de acuerdo con alguna regla desconocida para el tipo de elemento de la matriz. Hoy en día, es posible que incluso encuentre algunas aplicaciones en el mundo salvaje que dependen de este comportamiento, suponiendo que obtenga alguna información sobre la T
real en tiempo de ejecución mediante la inspección del tipo de matriz.
Esto lleva a algunas consecuencias interesantes:
static AutoCloseable[] ARR1;
static Serializable[] ARR2;
static <T extends AutoCloseable & Serializable> void method(T... args) {
ARR1 = args;
ARR2 = args;
}
public static void main(String[] args) throws Exception {
method(null, null);
ARR2[0] = "foo";
ARR1[0].close();
}
javac
decide crear una matriz del tipo real Serializable[]
aquí, a pesar de que el tipo de parámetro del método es AutoClosable[]
después de aplicar el borrado de tipo, que es la razón por la que la asignación de una String
es posible en tiempo de ejecución. Por lo tanto, solo fallará en la última instrucción cuando intente invocar el método close()
con
Exception in thread "main" java.lang.IncompatibleClassChangeError: Class java.lang.String does not implement the requested interface java.lang.AutoCloseable
Aquí se está culpando a la clase String
, aunque podríamos haber colocado cualquier objeto Serializable
en la matriz, ya que el problema es que un campo static
del tipo declarado formal AutoCloseable[]
refiere a un objeto del tipo real Serializable[]
.
Aunque es un comportamiento específico de la JVM HotSpot que hemos llegado hasta aquí, ya que su verificador no verifica las asignaciones cuando están involucrados los tipos de interfaz (incluidas las matrices de tipos de interfaz) sino que difiere la verificación de si la clase real implementa la interfaz hasta el último momento posible, cuando se intenta invocar un método de interfaz en él.
Curiosamente, los tipos de conversión son estrictos, cuando aparecen en el archivo de clase:
static <T extends AutoCloseable & Serializable> void method(T... args) {
AutoCloseable[] a = (AutoCloseable[])args; // actually removed by the compiler
a = (AutoCloseable[])(Object)args; // fails at runtime
}
public static void main(String[] args) throws Exception {
method();
}
Si bien la decisión de javac
para Serializable[]
en el ejemplo anterior parece arbitraria, debe quedar claro que independientemente del tipo que elija, una de las asignaciones de campo solo sería posible en una JVM con verificación de tipo laxa. También podríamos destacar la naturaleza más fundamental del problema:
// erased to method1(AutoCloseable[])
static <T extends AutoCloseable & Serializable> void method1(T... args) {
method2(args); // valid according to generic types
}
// erased to method2(Serializable[])
static <T extends Serializable & AutoCloseable> void method2(T... args) {
}
public static void main(String[] args) throws Exception {
// whatever array type the compiler picks, it would violate one of the erased types
method1();
}
Si bien esto no responde realmente a la pregunta que usa la regla real javac
(además de que usa "algo de T
"), enfatiza la importancia de tratar los arreglos creados para el parámetro varargs según lo previsto: un almacenamiento temporal (no asignar a campos) De un tipo arbitrario, es mejor que no te importe.
Normalmente, cuando el compilador encuentra una llamada a un método parametrizado, puede infiere el tipo ( JSL 18.5.2 ) y puede crear una matriz vararg correctamente escrita en el llamador.
Las reglas son en su mayoría formas técnicas de decir "encontrar todos los tipos de entrada posibles y verificarlos" (casos como void, ternary operator o lambda). El resto es de sentido común, como el uso de la clase base común más específica ( JSL 4.10.4 ). Ejemplo:
public class Test {
private static class A implements AutoCloseable, Runnable {
@Override public void close () throws Exception {}
@Override public void run () {} }
private static class B implements AutoCloseable, Runnable {
@Override public void close () throws Exception {}
@Override public void run () {} }
private static class C extends B {}
private static <T extends AutoCloseable & Runnable> void printType( T... args ) {
System.out.println( args.getClass().getComponentType().getSimpleName() );
}
public static void main( String[] args ) {
printType( new A() ); // A[] created here
printType( new B(), new B() ); // B[] created here
printType( new B(), new C() ); // B[] which is the common base class
printType( new A(), new B() ); // AutoCloseable[] - well...
printType(); // AutoCloseable[] - same as above
}
}
- JSL 18.2 dicta cómo procesar las restricciones para la inferencia de tipos, como
AutoCloseable & Channel
se reduce a solo elChannel
. Pero las reglas no ayudan a responder esta pregunta.
Obtener AutoCloseable[]
de la llamada puede parecer extraño, por supuesto, porque no podemos hacer eso con el código Java. Pero en realidad el tipo real no importa. En el nivel de lenguaje, args
es T[]
, donde T
es un "tipo virtual" que es tanto A como B ( JSL 4.9 ).
El compilador solo necesita asegurarse de que sus usos cumplan con todas las restricciones, y entonces sepa que la lógica es sólida y que no habrá ningún error de tipo (así es como se diseña Java genérico). Por supuesto, el compilador todavía necesita hacer una matriz real , y para el propósito crea una "matriz genérica". Por lo tanto, la advertencia " unchecked generic array creation
marcar " ( JLS 15.12.4.2 ).
En otras palabras, siempre que pase solo los AutoCloseable & Runnable
, y solo llamadas Object
, AutoCloseable
y Runnable
en printType
, el tipo de matriz real no importa. De hecho, los printType
de printType
de printType
serían los mismos, independientemente del tipo de matriz que se pase.
Dado que a printType
no le importa el tipo de matriz vararg, getComponentType()
no importa y no debería importar. Si desea obtener las interfaces, pruebe con getGenericInterfaces()
que devuelve una matriz.
- Debido al borrado de tipo ( JSL 4.6 ), el orden de las interfaces de
T
afecta la firma del método compilado y el código de bytes ( JSL 13.1 ). Se utilizará la primera interfazAutoClosable
, por ejemplo, no se realizará una verificación de tipo cuando se llame aAutoClosable.close()
enprintType
. - Pero esto no está relacionado con el tipo de interferencia de las llamadas de método de la pregunta, es decir, por qué se crea y pasa
AutoClosable[]
. Muchos dispositivos de seguridad de tipo se verifican antes de borrar, por lo que el orden no afecta la seguridad de tipo. Creo que esto es parte de lo que significa JSL por"The order of types... is only significant in that the erasure ... is determined by the first type"
( JSL 4.4 ). Significa que el orden es insignificante. - En cualquier caso, esta regla de borrado provoca que los casos de esquina, como agregar
printType(AutoCloseable[])
desencadenen un error de compilación, al agregarprintType( Runnable[])
no. Creo que este es un efecto secundario inesperado y está realmente fuera de alcance. - PS Excavar demasiado profundo puede causar insanity , teniendo en cuenta que creo que soy Ovis aries , ver la fuente en el ensamblaje y tener dificultades para responder en inglés en lugar de J̶́S͡L̴̀. Mi puntuación de cordura es byon̨d͝ r̨̡͝e̛a̕l̵ numb͟ers͡ . Volver. ̠̝͕B̭̳͠͡ͅẹ̡̬̦̙f͓͉̼̻o̼͕̱͎̬̟̪r҉͏̛̣̼͙͍͍̠̫͙ȩ̵̮̟̱̫͚ ̢͚̭̹̳̣̩̱͠..t̷҉̛̫͔͉̥͎̬ò̢̱̪͉̲͎͜o̭͈̩̖̭̬ .. ̮̘̯̗l̷̞͍͙̻̻͙̯̣͈̳͓͇a̸̢̢̰͓͓̪̳͉̯͉̼͝͝t̛̥̪̣̹̬͔̖͙̬̩̝̰͕̖̮̰̗͓̕͢ę̴̹̯̟͉̲͔͉̳̲̣͝͞.̬͖͖͇͈̤̼͖́͘͢.͏̪̱̝̠̯̬͍̘̣̩͉̯̹̼͟͟͠.̨͠҉̬̘̹ͅ