Respuestas:
Es una nueva instrucción JVM que permite un compilador para generar código que llama a los métodos con una especificación más suelta que antes era posible - si usted sabe lo que " duck typing " es, básicamente invokedynamic permite escribir pato. No hay mucho que usted como programador de Java pueda hacer con él; sin embargo, si es un creador de herramientas, puede usarlo para construir lenguajes basados en JVM más flexibles y eficientes. Aquí hay una publicación de blog realmente dulce que da muchos detalles.
MethodHandle
, que es realmente el mismo tipo de cosas pero con mucha más flexibilidad. Pero el verdadero poder de todo esto no se suma al lenguaje Java, sino a las capacidades de la propia JVM para admitir otros lenguajes que son intrínsecamente más dinámicos.
invokedynamic
lo que lo hace eficiente (en comparación con envolverlas en una clase interna anónima que era casi la única opción antes de la introducción invokedynamic
). Lo más probable es que muchos lenguajes de programación funcionales además de JVM opten por compilar esto en lugar de anon-inner-classes.
Hace algún tiempo, C # agregó una característica genial, sintaxis dinámica dentro de C #
Object obj = ...; // no static type available
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.
Piense en ello como el azúcar de sintaxis para las llamadas a métodos reflexivos. Puede tener aplicaciones muy interesantes. ver http://www.infoq.com/presentations/Statical-Dynamic-Typing-Neal-Gafter
Neal Gafter, responsable del tipo dinámico de C #, acaba de desertar de SUN a MS. Por lo tanto, no es irracional pensar que se hayan discutido las mismas cosas dentro de SUN.
Recuerdo que poco después, un tipo de Java anunció algo similar
InvokeDynamic duck = obj;
duck.quack();
Desafortunadamente, la característica no se encuentra en Java 7. Muy decepcionado. Para los programadores de Java, no tienen una manera fácil de aprovechar invokedynamic
sus programas.
invokedynamic
nunca fue diseñado para ser utilizado por programadores Java. En mi opinión, no se ajusta a la filosofía de Java en absoluto. Se agregó como una función JVM para lenguajes no Java.
Hay dos conceptos para entender antes de continuar invocando dinámicamente.
1. Mecanografía estática vs. Dynamin
Estático : comprobación de tipo de preformas en tiempo de compilación (por ejemplo, Java)
Dinámico : comprobación de tipo de preformas en tiempo de ejecución (por ejemplo, JavaScript)
La verificación de tipos es un proceso para verificar que un programa es seguro para los tipos, es decir, verificar la información escrita para las variables de clase e instancia, parámetros de método, valores de retorno y otras variables. Por ejemplo, Java conoce int, String, .. en tiempo de compilación, mientras que el tipo de un objeto en JavaScript solo se puede determinar en tiempo de ejecución
2. Mecanografía fuerte contra débil
Fuerte : especifica restricciones sobre los tipos de valores suministrados a sus operaciones (por ejemplo, Java)
Débil : convierte (convierte) los argumentos de una operación si esos argumentos tienen tipos incompatibles (por ejemplo, Visual Basic)
Sabiendo que Java es un tipo de escritura estática y débil, ¿cómo implementa lenguajes de tipo dinámico y fuerte en la JVM?
El invocador dinámico implementa un sistema de tiempo de ejecución que puede elegir la implementación más apropiada de un método o función, una vez que el programa ha sido compilado.
Ejemplo: Tener (a + b) y no saber nada sobre las variables a, b en tiempo de compilación, invoca dinámicamente esta operación al método más apropiado en Java en tiempo de ejecución. Por ejemplo, si resulta que a, b son Strings, entonces llame al método (String a, String b). Si resulta que a, b son ints, entonces llame al método (int a, int b).
invocador dinámico se introdujo con Java 7.
Como parte de mi artículo de Java Records , articulé sobre la motivación detrás de Inoke Dynamic. Comencemos con una definición aproximada de Indy.
Invoke Dynamic (también conocido como Indy ) era parte de JSR 292 con la intención de mejorar el soporte de JVM para Dynamic Type Languages. Después de su primer lanzamiento en Java 7, el invokedynamic
código de operación junto con su java.lang.invoke
equipaje es utilizado ampliamente por lenguajes dinámicos basados en JVM como JRuby.
Aunque indy está específicamente diseñado para mejorar el soporte dinámico del lenguaje, ofrece mucho más que eso. De hecho, es adecuado para usar donde sea que un diseñador de idiomas necesite cualquier forma de dinámica, desde acrobacias de tipo dinámico hasta estrategias dinámicas.
Por ejemplo, las expresiones Lambda de Java 8 se implementan realmente usando invokedynamic
, ¡aunque Java es un lenguaje estáticamente tipado!
Durante bastante tiempo, JVM admitió cuatro tipos de invocación de métodos: invokestatic
llamar a métodos estáticos, invokeinterface
llamar a métodos de interfaz, invokespecial
llamar a constructores super()
o métodos privados y invokevirtual
llamar a métodos de instancia.
A pesar de sus diferencias, estos tipos de invocación comparten un rasgo común: no podemos enriquecerlos con nuestra propia lógica . Por el contrario, invokedynamic
nos permite Bootstrap el proceso de invocación de la forma que queramos. Luego, la JVM se encarga de llamar directamente al Método Bootstrapped.
La primera vez que JVM ve una invokedynamic
instrucción, llama a un método estático especial llamado Método Bootstrap . El método bootstrap es un fragmento de código Java que hemos escrito para preparar la lógica real a invocar:
Luego, el método bootstrap devuelve una instancia de java.lang.invoke.CallSite
. Esto CallSite
contiene una referencia al método real, es decir MethodHandle
.
A partir de ahora, cada vez que JVM vuelve a ver esta invokedynamic
instrucción, omite la ruta lenta y llama directamente al ejecutable subyacente. La JVM continúa omitiendo el camino lento a menos que algo cambie.
Java 14 Records
proporciona una buena sintaxis compacta para declarar clases que se supone que son titulares de datos tontos.
Considerando este simple registro:
public record Range(int min, int max) {}
El código de bytes para este ejemplo sería algo como:
Compiled from "Range.java"
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #18, 0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
6: areturn
En su tabla de método Bootstrap :
BootstrapMethods:
0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
Method arguments:
#8 Range
#48 min;max
#50 REF_getField Range.min:I
#51 REF_getField Range.max:I
Entonces se llama al método bootstrap para Records bootstrap
que reside en la java.lang.runtime.ObjectMethods
clase. Como puede ver, este método de arranque espera los siguientes parámetros:
MethodHandles.Lookup
representación del contexto de búsqueda (La Ljava/lang/invoke/MethodHandles$Lookup
parte).toString
, equals
, hashCode
, etc.) el bootstrap va a enlace. Por ejemplo, cuando el valor es toString
, bootstrap devolverá un ConstantCallSite
(a CallSite
que nunca cambia) que apunta a la toString
implementación real de este Registro en particular.TypeDescriptor
para el método ( Ljava/lang/invoke/TypeDescriptor
parte).Class<?>
, que representa el tipo de clase Record. Es
Class<Range>
en este caso.min;max
.MethodHandle
por componente. De esta forma, el método bootstrap puede crear un método MethodHandle
basado en los componentes para la implementación de este método en particular.La invokedynamic
instrucción pasa todos esos argumentos al método bootstrap. El método Bootstrap, a su vez, devuelve una instancia de ConstantCallSite
. Esto ConstantCallSite
contiene una referencia a la implementación del método solicitado, por ejemplo toString
.
A diferencia de las API de Reflection, la java.lang.invoke
API es bastante eficiente ya que la JVM puede ver completamente todas las invocaciones. Por lo tanto, JVM puede aplicar todo tipo de optimizaciones siempre que evitemos la ruta lenta tanto como sea posible.
Además del argumento de la eficiencia, el invokedynamic
enfoque es más confiable y menos frágil debido a su simplicidad .
Además, el bytecode generado para Java Records es independiente del número de propiedades. Por lo tanto, menos bytecode y un tiempo de inicio más rápido.
Finalmente, supongamos que una nueva versión de Java incluye una implementación de método de arranque nueva y más eficiente. Con invokedynamic
, nuestra aplicación puede aprovechar esta mejora sin recompilación. De esta manera tenemos algún tipo de compatibilidad binaria directa . Además, ¡esa es la estrategia dinámica de la que estábamos hablando!
Además de Java Records, la dinámica de invocación se ha utilizado para implementar características como:
LambdaMetafactory
StringConcatFactory
meth.invoke(args)
. Entonces, ¿cómoinvokedynamic
encajameth.invoke
?