util una tiempo temporizador segundos reloj para método metodo hora hacer especifica ejemplo ejecutar cronometro como codigo cierto cada alarma java variadic-functions jls multiple-bounds

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 de m necesariamente tiene el tipo T[] para algunos T , y m se invoca necesariamente con k ≥ 0 expresiones de argumentos reales.

Si m se invoca con k n n expresiones de argumento reales, o si m se invoca con k = n expresiones de argumento real y el tipo de k ''th expresión de argumento no es compatible con T[] , 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) de T[] .

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 el Channel . 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 interfaz AutoClosable , por ejemplo, no se realizará una verificación de tipo cuando se llame a AutoClosable.close() en printType .
  • 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 agregar printType( 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̛̥̪̣̹̬͔̖͙̬̩̝̰͕̖̮̰̗͓̕͢ę̴̹̯̟͉̲͔͉̳̲̣͝͞.̬͖͖͇͈̤̼͖́͘͢.͏̪̱̝̠̯̬͍̘̣̩͉̯̹̼͟͟͠.̨͠҉̬̘̹ͅ