Odpowiedzi:
Jest to nowa instrukcja JVM, która pozwala kompilatorowi na generowanie kodu, który wywołuje metody o luźniejszej specyfikacji niż było to wcześniej możliwe - jeśli wiesz, co to jest " kaczkowate pisanie ", invokedynamic w zasadzie pozwala na kaczkę. Jako programista Java nie może z tym zrobić zbyt wiele; Jeśli jednak jesteś twórcą narzędzi, możesz go użyć do tworzenia bardziej elastycznych i wydajnych języków opartych na JVM. Oto naprawdę słodki wpis na blogu, który zawiera wiele szczegółów.
MethodHandle
, o którym wspominam, jest mowa o tym samym rodzaju, ale z dużo większą elastycznością. Ale prawdziwa siła tkwi w tym wszystkim nie w dodatkach do języka Java, ale w możliwościach samej maszyny JVM we wspieraniu innych języków, które są z natury bardziej dynamiczne.
invokedynamic
co czyni ją wydajną (w porównaniu z opakowaniem ich w anonimową klasę wewnętrzną, która była prawie jedynym wyborem przed wprowadzeniem invokedynamic
). Najprawdopodobniej wiele funkcjonalnych języków programowania korzystających z JVM zdecyduje się na kompilację do tego zamiast do anon-internal-classes.
Jakiś czas temu C # dodał fajną funkcję, dynamiczną składnię w C #
Object obj = ...; // no static type available
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.
Potraktuj to jako lukę składniową dla wywołań metod refleksyjnych. Może mieć bardzo ciekawe zastosowania. zobacz http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
Neal Gafter, który jest odpowiedzialny za dynamiczny typ C #, właśnie przeszedł z SUN do MS. Nie jest więc nierozsądne myślenie, że te same rzeczy zostały omówione w SUN.
Pamiętam, że wkrótce potem jakiś koleś z Javy ogłosił coś podobnego
InvokeDynamic duck = obj;
duck.quack();
Niestety, tej funkcji nie ma w Javie 7. Bardzo rozczarowany. Programiści Java nie mają łatwego sposobu wykorzystania invokedynamic
ich w swoich programach.
invokedynamic
nigdy nie był przeznaczony do użytku przez programistów Java. IMO w ogóle nie pasuje do filozofii Java. Został dodany jako funkcja JVM dla języków innych niż Java.
Przed kontynuacją przywoływania dynamiki należy zrozumieć dwie koncepcje.
1. Typowanie statyczne a Dynamin
Statyczny - sprawdzanie typu preform w czasie kompilacji (np. Java)
Dynamiczne - sprawdzanie typu preform w czasie wykonywania (np. JavaScript)
Sprawdzanie typu to proces sprawdzania, czy program jest bezpieczny, czyli sprawdzanie wpisanych informacji pod kątem zmiennych klas i instancji, parametrów metod, wartości zwracanych i innych zmiennych. Np. Java wie o int, String, .. w czasie kompilacji, podczas gdy typ obiektu w JavaScript można określić tylko w czasie wykonywania
2. Silne i słabe pisanie
Strong - określa ograniczenia dotyczące typów wartości dostarczanych do jego operacji (np. Java)
Weak - konwertuje (rzuca) argumenty operacji, jeśli te argumenty mają niekompatybilne typy (np. Visual Basic)
Wiedząc, że Java jest typem statycznym i słabo typowanym, jak zaimplementować języki dynamicznie i silnie typowane w JVM?
Invokedynamic implementuje system wykonawczy, który może wybrać najbardziej odpowiednią implementację metody lub funkcji - po skompilowaniu programu.
Przykład: mając (a + b) i nie wiedząc nic o zmiennych a, b w czasie kompilacji, invokedynamic mapuje tę operację na najbardziej odpowiednią metodę w Javie w czasie wykonywania. Na przykład, jeśli okaże się, że a, b są ciągami znaków, wywołaj metodę (String a, String b). Jeśli okaże się, że a, b są ints, wywołaj metodę (int a, int b).
invokedynamic został wprowadzony w Javie 7.
W ramach mojego artykułu o Java Records wypowiedziałem się na temat motywacji stojącej za Inoke Dynamic. Zacznijmy od przybliżonej definicji Indy.
Invoke Dynamic (znany również jako Indy ) był częścią JSR 292, którego celem było ulepszenie obsługi JVM dla dynamicznych języków typu. Po pierwszym wydaniu w Javie 7 invokedynamic
kod operacji wraz z jego java.lang.invoke
bagażem jest używany dość intensywnie przez dynamiczne języki oparte na JVM, takie jak JRuby.
Chociaż indy został specjalnie zaprojektowany, aby zwiększyć obsługę dynamicznego języka, oferuje znacznie więcej. W rzeczywistości nadaje się do użycia wszędzie tam, gdzie projektant języka potrzebuje jakiejkolwiek formy dynamiki, od dynamicznej akrobatyki po dynamiczne strategie!
Na przykład, wyrażenia Lambda Java 8 są faktycznie implementowane przy użyciu invokedynamic
, mimo że Java jest językiem z typowaniem statycznym!
Przez jakiś czas JVM obsługiwała cztery typy wywołań metod: invokestatic
wywoływanie metod statycznych, invokeinterface
wywoływanie metod interfejsu, invokespecial
wywoływanie konstruktorów super()
lub metody prywatne i invokevirtual
wywoływanie metod instancji.
Pomimo różnic, te typy inwokacji mają jedną wspólną cechę: nie możemy ich wzbogacić naszą własną logiką . Wręcz przeciwnie, invokedynamic
umożliwia nam załadowanie procesu wywołania w dowolny sposób. Następnie JVM dba o bezpośrednie wywołanie metody Bootstrapped.
Gdy maszyna JVM po raz pierwszy widzi invokedynamic
instrukcję, wywołuje specjalną metodę statyczną o nazwie Metoda Bootstrap . Metoda bootstrap to fragment kodu Java, który napisaliśmy, aby przygotować rzeczywistą logikę do wywołania:
Następnie metoda bootstrap zwraca wystąpienie java.lang.invoke.CallSite
. Odnosi się CallSite
to do faktycznej metody, tj MethodHandle
.
Odtąd za każdym razem, gdy JVM invokedynamic
ponownie zobaczy tę instrukcję, pomija powolną ścieżkę i bezpośrednio wywołuje podstawowy plik wykonywalny. JVM nadal omija powolną ścieżkę, chyba że coś się zmieni.
Java 14 Records
zapewnia ładną, zwartą składnię do deklarowania klas, które mają być głupimi posiadaczami danych.
Biorąc pod uwagę ten prosty rekord:
public record Range(int min, int max) {}
Kod bajtowy dla tego przykładu wyglądałby tak:
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
W tabeli metod 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
Dlatego wywoływana jest metoda ładowania dla rekordów, bootstrap
która znajduje się w java.lang.runtime.ObjectMethods
klasie. Jak widać, ta metoda ładowania początkowego wymaga następujących parametrów:
MethodHandles.Lookup
reprezentujące kontekst wyszukiwania ( Ljava/lang/invoke/MethodHandles$Lookup
część).toString
, equals
, hashCode
, itd.) Bootstrap będzie link. Na przykład, gdy wartością jest toString
, funkcja bootstrap zwróci ConstantCallSite
(a, CallSite
które nigdy się nie zmienia), które wskazuje na rzeczywistą toString
implementację tego konkretnego rekordu.TypeDescriptor
Dla sposobu ( Ljava/lang/invoke/TypeDescriptor
część).Class<?>
Reprezentujący typ klasy Record. Tak jest
Class<Range>
w tym przypadku.min;max
.MethodHandle
na składnik. W ten sposób metoda bootstrap może utworzyć na MethodHandle
podstawie komponentów tej konkretnej implementacji metody.invokedynamic
Instrukcja przechodzi wszystkie te argumenty metody bootstrap. Z kolei metoda Bootstrap zwraca instancję ConstantCallSite
. Ten ConstantCallSite
trzyma odniesienie do żądanej metody realizacji, np toString
.
W przeciwieństwie do interfejsów API Reflection, interfejs java.lang.invoke
API jest dość wydajny, ponieważ maszyna JVM może w pełni przejrzeć wszystkie wywołania. Dlatego JVM może stosować wszelkiego rodzaju optymalizacje, o ile unikamy powolnej ścieżki tak długo, jak to możliwe!
Oprócz argumentu dotyczącego wydajności, invokedynamic
podejście to jest bardziej niezawodne i mniej kruche ze względu na swoją prostotę .
Ponadto generowany kod bajtowy dla Java Records jest niezależny od liczby właściwości. Tak więc mniej kodu bajtowego i krótszy czas uruchamiania.
Na koniec załóżmy, że nowa wersja Java zawiera nową i bardziej wydajną implementację metody ładowania początkowego. Dzięki invokedynamic
naszej aplikacji możemy skorzystać z tego ulepszenia bez ponownej kompilacji. W ten sposób mamy pewnego rodzaju kompatybilność binarną do przodu . Poza tym to dynamiczna strategia, o której mówiliśmy!
Oprócz Java Records, dynamika wywołania została wykorzystana do implementacji funkcji takich jak:
LambdaMetafactory
StringConcatFactory
meth.invoke(args)
. Więc jak toinvokedynamic
pasujemeth.invoke
?