Cos'è invokedynamic e come lo uso?


159

Continuo a sentir parlare di tutte le nuove fantastiche funzioni che vengono aggiunte alla JVM e una di quelle fantastiche funzioni è invocata. Vorrei sapere di cosa si tratta e come semplifica o migliora la programmazione riflessiva in Java?

Risposte:


165

Si tratta di una nuova istruzione JVM che consente a un compilatore di generare codice che chiama metodi con una specifica più libera di quanto fosse possibile in precedenza - se sai cos'è la " tipizzazione anatra ", invokedynamic sostanzialmente consente la tipizzazione anatra. Non c'è molto che tu possa fare un programmatore Java; se sei un creatore di strumenti, tuttavia, puoi utilizzarlo per creare linguaggi basati su JVM più flessibili ed efficienti. Ecco un post sul blog davvero dolce che offre molti dettagli.


3
Nella programmazione quotidiana di Java non è raro vedere la riflessione usata per invocare metodi dinamicamente meth.invoke(args). Quindi come si invokedynamicadatta meth.invoke?
David K.

1
Il post sul blog di cui parlo parla MethodHandle, che è davvero lo stesso tipo di cosa ma con molta più flessibilità. Ma il vero potere in tutto ciò non deriva dalle aggiunte al linguaggio Java, ma dalle capacità della JVM stessa nel supportare altri linguaggi intrinsecamente più dinamici.
Ernest Friedman-Hill il


1
Sembra che Java 8 traduca alcuni dei lambda invokedynamicche li rendono performanti (rispetto a avvolgerli in una classe interna anonima che era quasi l'unica scelta prima di introdurre invokedynamic). Molto probabilmente molti linguaggi di programmazione funzionale su JVM sceglieranno di compilare questo invece di classi anon-inner.
Nader Ghanbari,

2
Solo un piccolo avvertimento, quel post sul blog del 2008 è irrimediabilmente obsoleto e non riflette lo stato di rilascio effettivo (2011).
Holger,

9

Qualche tempo fa, C # ha aggiunto una funzione interessante, sintassi dinamica all'interno di C #

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

Pensalo come zucchero di sintassi per chiamate a metodi riflessivi. Può avere applicazioni molto interessanti. vedi http://www.infoq.com/presentations/StATIC-Dynamic-Typing-Neal-Gafter

Neal Gafter, che è responsabile del tipo dinamico di C #, è appena passato da SUN a MS. Quindi non è irragionevole pensare che le stesse cose siano state discusse in SUN.

Ricordo subito dopo che alcuni tizi di Java hanno annunciato qualcosa di simile

InvokeDynamic duck = obj;
duck.quack(); 

Sfortunatamente, la funzione non è dove trovarsi in Java 7. Molto deluso. Per i programmatori Java, non hanno modo di trarre vantaggio dai invokedynamicloro programmi.


41
invokedynamicnon è mai stato progettato per essere utilizzato per programmatori Java. IMO non si adatta affatto alla filosofia Java. È stato aggiunto come funzionalità JVM per linguaggi non Java.
Mark Peters,

5
@Mark Mai inteso da chi? Non è che ci sia una chiara struttura di potere nelle celebrità in linguaggio Java, o una "intenzione" collettiva ben definita. Per quanto riguarda la filosofia del linguaggio - è abbastanza fattibile, vedi la spiegazione di Neal Gafter (traditore!): Infoq.com/presentations/Static-Dynamic-Typing-Neal-Gafter
irreputabile

3
@mark peters: invokedynamic è in realtà destinato anche ai programmatori Java non direttamente accessibili. È la base per le chiusure di Java 8.
M Platvoet,

2
@irreputable: mai inteso dai collaboratori di JSR. Sta dicendo che il nome di JSR è "Supporting Dynamically Typed Languages ​​on the Java Platform". Java non è un linguaggio tipizzato in modo dinamico.
Mark Peters,

5
@M Platvoet: non sono stato aggiornato con le chiusure, ma certamente non sarebbe un requisito assoluto per le chiusure. Un'altra opzione di cui hanno discusso è stata semplicemente la produzione di zucchero sintattico per chiusure per classi interne anonime che poteva essere fatto senza una modifica delle specifiche della VM. Ma il mio punto era che il JSR non era mai stato progettato per portare la tipizzazione dinamica nel linguaggio Java, questo è molto chiaro se si legge il JSR.
Mark Peters,

4

Ci sono due concetti da capire prima di continuare a invocinodinamico.

1. Digitazione statica vs. Dynamin

Statico - controllo del tipo di preforme al momento della compilazione (ad es. Java)

Dinamico : controllo del tipo di preforme in fase di esecuzione (ad es. JavaScript)

Il controllo del tipo è un processo per verificare che un programma sia sicuro, ovvero per verificare le informazioni digitate per variabili di classe e di istanza, parametri di metodo, valori restituiti e altre variabili. Ad esempio Java conosce int, String, .. al momento della compilazione, mentre il tipo di un oggetto in JavaScript può essere determinato solo in fase di esecuzione

2. Digitazione forte vs. debole

Forte : specifica le restrizioni sui tipi di valori forniti alle sue operazioni (ad es. Java)

Debole : converte (cast) argomenti di un'operazione se tali argomenti hanno tipi incompatibili (ad esempio Visual Basic)

Sapendo che Java è tipicamente e tipicamente debole, come si implementano i linguaggi tipizzati dinamicamente e fortemente sulla JVM?

Invokedynamic implementa un sistema di runtime che può scegliere l'implementazione più appropriata di un metodo o di una funzione - dopo che il programma è stato compilato.

Esempio: Avere (a + b) e non sapere nulla delle variabili a, b al momento della compilazione, invokedynamic mappa questa operazione al metodo più appropriato in Java in fase di runtime. Ad esempio, se risulta che a, b sono stringhe, quindi chiamare il metodo (stringa a, stringa b). Se risulta che a, b sono ints, quindi chiama il metodo (int a, int b).

invokedynamic è stato introdotto con Java 7.


4

Come parte del mio articolo su Java Records , ho articolato la motivazione alla base di Inoke Dynamic. Cominciamo con una definizione approssimativa di Indy.

Presentazione di Indy

Invoke Dynamic (noto anche come Indy ) faceva parte di JSR 292 con l' intenzione di migliorare il supporto JVM per Dynamic Type Languages. Dopo la sua prima versione in Java 7, The invokedynamicopcode insieme al suo java.lang.invokebagaglio è ampiamente utilizzato da linguaggi dinamici basati su JVM come JRuby.

Sebbene Indy sia progettato specificamente per migliorare il supporto del linguaggio dinamico, offre molto di più. È un dato di fatto, è adatto all'uso ovunque un designer linguistico abbia bisogno di qualsiasi forma di dinamica, dalle acrobazie di tipo dinamico alle strategie dinamiche!

Ad esempio, le espressioni lambda Java 8 vengono effettivamente implementate utilizzando invokedynamic, anche se Java è un linguaggio tipicamente statico!

Bytecode definibile dall'utente

Per un po 'di tempo JVM ha supportato quattro tipi di invocazione di metodi: invokestaticchiamare metodi statici, invokeinterfacechiamare metodi di interfaccia, invokespecialchiamare costruttori super()o metodi privati ​​e invokevirtualchiamare metodi di istanza.

Nonostante le loro differenze, questi tipi di invocazione condividono un tratto comune: non possiamo arricchirli con la nostra logica . Al contrario, invokedynamic ci consente di avviare il processo di invocazione nel modo che desideriamo. Quindi la JVM si occupa di chiamare direttamente il metodo Bootstrapped.

Come funziona Indy?

La prima volta che JVM vede invokedynamicun'istruzione, chiama uno speciale metodo statico chiamato Bootstrap Method . Il metodo bootstrap è un pezzo di codice Java che abbiamo scritto per preparare l'attuale logica da invocare:

inserisci qui la descrizione dell'immagine

Quindi il metodo bootstrap restituisce un'istanza di java.lang.invoke.CallSite. Questo CallSitecontiene un riferimento al metodo effettivo, ovvero MethodHandle.

Da ora in poi, ogni volta che JVM vede di invokedynamicnuovo queste istruzioni, salta il percorso lento e chiama direttamente l'eseguibile sottostante. La JVM continua a saltare il percorso lento a meno che qualcosa non cambi.

Esempio: Java 14 Records

Java 14 Recordsfornisce una bella sintassi compatta per dichiarare le classi che dovrebbero essere titolari di dati stupidi.

Considerando questo semplice record:

public record Range(int min, int max) {}

Il bytecode per questo esempio sarebbe qualcosa del tipo:

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

Nella sua tabella dei metodi 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

Quindi viene chiamato il metodo bootstrap per Records bootstrapche risiede nella java.lang.runtime.ObjectMethodsclasse. Come puoi vedere, questo metodo bootstrap prevede i seguenti parametri:

  • Un'istanza di MethodHandles.Lookuprappresentazione del contesto di ricerca (La Ljava/lang/invoke/MethodHandles$Lookupparte).
  • Il nome del metodo (cioè toString, equals, hashCode, etc.) il bootstrap sta per collegamento. Ad esempio, quando il valore è toString, bootstrap restituirà un ConstantCallSite(a CallSiteche non cambia mai) che punta toStringall'implementazione effettiva per questo particolare Record.
  • Il TypeDescriptorper il metodo ( Ljava/lang/invoke/TypeDescriptor parte).
  • Un token di tipo, ovvero che Class<?>rappresenta il tipo di classe Record. È Class<Range>in questo caso.
  • Un elenco separato da punti e virgola di tutti i nomi dei componenti, ad es min;max.
  • Uno MethodHandleper componente. In questo modo il metodo bootstrap può creare un MethodHandlebasato sui componenti per questa particolare implementazione del metodo.

L' invokedynamicistruzione passa tutti quegli argomenti al metodo bootstrap. Il metodo Bootstrap, a sua volta, restituisce un'istanza di ConstantCallSite. Questo ConstantCallSitecontiene un riferimento all'implementazione del metodo richiesto, ad es toString.

Perché Indy?

A differenza delle API di Reflection, l' java.lang.invokeAPI è abbastanza efficiente poiché JVM può vedere completamente tutte le invocazioni. Pertanto, JVM può applicare tutti i tipi di ottimizzazioni purché evitiamo il percorso lento il più possibile!

Oltre all'argomento dell'efficienza, l' invokedynamicapproccio è più affidabile e meno fragile a causa della sua semplicità .

Inoltre, il bytecode generato per Java Records è indipendente dal numero di proprietà. Quindi, meno bytecode e tempi di avvio più rapidi.

Supponiamo infine che una nuova versione di Java includa un'implementazione del metodo bootstrap nuova e più efficiente. Con invokedynamic, la nostra app può sfruttare questo miglioramento senza ricompilazione. In questo modo abbiamo una sorta di compatibilità binaria diretta . Inoltre, questa è la strategia dinamica di cui stavamo parlando!

Altri esempi

Oltre a Java Records, la dinamica invoke è stata utilizzata per implementare funzionalità come:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.