¿Qué se invoca dinámico y cómo lo uso?

159

Sigo escuchando acerca de todas las nuevas características interesantes que se están agregando a la JVM y una de esas características interesantes es invocada de forma dinámica. Me gustaría saber qué es y cómo hace que la programación reflexiva en Java sea más fácil o mejor.

David K.
fuente

Respuestas:

165

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.

Ernest Friedman-Hill
fuente
3
En la programación diaria de Java, no es raro ver la reflexión que se utiliza para invocar métodos dinámicamente meth.invoke(args). Entonces, ¿cómo invokedynamicencaja meth.invoke?
David K.
1
La publicación de blog que menciono habla 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.
Ernest Friedman-Hill
1
Parece que Java 8 traduce algunas de las lambdas usando invokedynamiclo 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.
Nader Ghanbari
2
Solo una pequeña advertencia, esa publicación de blog de 2008 está irremediablemente desactualizada y no refleja el estado de lanzamiento real (2011).
Holger
9

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 invokedynamicsus programas.

irreputable
fuente
41
invokedynamicnunca 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.
Mark Peters
55
@ Mark ¿Nunca quiso quién? No es que haya una estructura de poder clara en las celebridades del lenguaje Java, o que haya una "intención" colectiva bien definida. En cuanto a la filosofía del lenguaje: es bastante factible, vea la explicación de Neal Gafter (¡traidor!): Infoq.com/presentations/Static-Dynamics-Typing-Neal-Gafter
irreputable
3
@mark peters: invocados dinámicos en realidad también está destinado a programadores de Java que no son directamente accesibles. Es la base para los cierres de Java 8.
M Platvoet
2
@irreputable: Nunca previsto por los contribuyentes de JSR. Es revelador que el nombre de JSR sea "Soporte de lenguajes de escritura dinámica en la plataforma Java". Java no es un lenguaje de tipo dinámico.
Mark Peters
55
@M Platvoet: No me he mantenido al día con los cierres, pero ciertamente no sería un requisito absoluto para los cierres. Otra opción que discutieron fue simplemente hacer cierres de azúcar sintáctico para clases internas anónimas que podrían hacerse sin un cambio de especificación de VM. Pero mi punto era que el JSR nunca tuvo la intención de llevar la escritura dinámica al lenguaje Java, eso está claro si lees el JSR.
Mark Peters
4

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.

Sabina Orazem
fuente
4

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.

Introduciendo 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 invokedynamiccódigo de operación junto con su java.lang.invokeequipaje 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!

Código de bytes definible por el usuario

Durante bastante tiempo, JVM admitió cuatro tipos de invocación de métodos: invokestaticllamar a métodos estáticos, invokeinterfacellamar a métodos de interfaz, invokespecialllamar a constructores super()o métodos privados y invokevirtualllamar 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.

¿Cómo funciona Indy?

La primera vez que JVM ve una invokedynamicinstrucció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:

ingrese la descripción de la imagen aquí

Luego, el método bootstrap devuelve una instancia de java.lang.invoke.CallSite. Esto CallSitecontiene una referencia al método real, es decir MethodHandle.

A partir de ahora, cada vez que JVM vuelve a ver esta invokedynamicinstrucción, omite la ruta lenta y llama directamente al ejecutable subyacente. La JVM continúa omitiendo el camino lento a menos que algo cambie.

Ejemplo: registros Java 14

Java 14 Recordsproporciona 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 bootstrapque reside en la java.lang.runtime.ObjectMethodsclase. Como puede ver, este método de arranque espera los siguientes parámetros:

  • Una instancia de MethodHandles.Lookuprepresentación del contexto de búsqueda (La Ljava/lang/invoke/MethodHandles$Lookupparte).
  • El nombre del método (es decir toString, equals, hashCode, etc.) el bootstrap va a enlace. Por ejemplo, cuando el valor es toString, bootstrap devolverá un ConstantCallSite(a CallSiteque nunca cambia) que apunta a la toStringimplementación real de este Registro en particular.
  • El TypeDescriptorpara el método ( Ljava/lang/invoke/TypeDescriptor parte).
  • Un token de tipo, es decir Class<?>, que representa el tipo de clase Record. Es Class<Range>en este caso.
  • Una lista separada por punto y coma de todos los nombres de componentes, es decir min;max.
  • Uno MethodHandlepor componente. De esta forma, el método bootstrap puede crear un método MethodHandlebasado en los componentes para la implementación de este método en particular.

La invokedynamicinstrucción pasa todos esos argumentos al método bootstrap. El método Bootstrap, a su vez, devuelve una instancia de ConstantCallSite. Esto ConstantCallSitecontiene una referencia a la implementación del método solicitado, por ejemplo toString.

¿Por qué indy?

A diferencia de las API de Reflection, la java.lang.invokeAPI 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 invokedynamicenfoque 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!

Otros ejemplos

Además de Java Records, la dinámica de invocación se ha utilizado para implementar características como:

Ali Dehghani
fuente