Java 8 wprowadza ważne nowe funkcje językowe, takie jak wyrażenia lambda.
Czy tym zmianom w języku towarzyszą tak znaczące zmiany w skompilowanym kodzie bajtowym, które uniemożliwiłyby uruchomienie go na maszynie wirtualnej Java 7 bez użycia retrotranslatora?
Odpowiedzi:
Nie, użycie funkcji 1.8 w kodzie źródłowym wymaga docelowej maszyny wirtualnej 1.8. Właśnie wypróbowałem nową wersję Java 8 i próbowałem kompilować z
-target 1.7 -source 1.8
, a kompilator odmawia:źródło
Domyślne metody wymagają takich zmian w kodzie bajtowym i JVM, których nie byłoby możliwe w Javie 7. Weryfikator kodu bajtowego w Javie 7 i starszych odrzuci interfejsy z treściami metod (z wyjątkiem statycznej metody inicjatora). Próba emulacji metod domyślnych z metodami statycznymi po stronie obiektu wywołującego nie przyniosłaby takich samych wyników, ponieważ metody domyślne można przesłonić w podklasach. Retrolambda ma ograniczone wsparcie dla domyślnych metod backportowania, ale nigdy nie może być w pełni backportowany, ponieważ naprawdę wymaga nowych funkcji JVM.
Lambdy mogłyby działać na Javie 7 w takiej postaci, w jakiej są, gdyby po prostu istniały tam niezbędne klasy API. Instrukcja invokedynamic istnieje w Javie 7, ale byłoby możliwe zaimplementowanie lambda tak, aby generował klasy lambda w czasie kompilacji (wczesne kompilacje JDK 8 robiły to w ten sposób), w którym to przypadku działałaby na dowolnej wersji Javy. (Firma Oracle zdecydowała się na użycie invokedynamic dla lambd do sprawdzania przyszłych zmian; być może pewnego dnia JVM będzie miał funkcje pierwszej klasy, więc wtedy invokedynamic będzie można zmienić tak, aby używał ich zamiast generowania klasy dla każdej lambdy, poprawiając w ten sposób wydajność). że przetwarza wszystkie wywoływane instrukcje dynamiczne i zastępuje je klasami anonimowymi; tak samo, jak robi to Java 8 w czasie wykonywania, gdy wywołanie lamdba dynamic jest wywoływane po raz pierwszy.
Powtarzanie adnotacji to po prostu cukier syntaktyczny. Są zgodne z kodem bajtowym z poprzednimi wersjami. W Javie 7 wystarczyłoby zaimplementować samodzielnie metody pomocnicze (np. GetAnnotationsByType ), które ukrywają szczegóły implementacji adnotacji kontenera zawierającej powtarzające się adnotacje.
AFAIK, adnotacje typu istnieją tylko w czasie kompilacji, więc nie powinny wymagać zmiany kodu bajtowego, więc sama zmiana numeru wersji kodu bajtowego skompilowanych klas Java 8 powinna wystarczyć, aby działały w Javie 7.
Nazwy parametrów metod istnieją w kodzie bajtowym w Javie 7, więc jest to również zgodne. Możesz uzyskać do nich dostęp, czytając kod bajtowy metody i przeglądając nazwy zmiennych lokalnych w informacjach debugowania metody. Na przykład Spring Framework robi dokładnie to, aby zaimplementować @PathVariable , więc prawdopodobnie istnieje metoda biblioteczna, którą możesz wywołać. Ponieważ abstrakcyjne metody interfejsu nie mają treści metody, te informacje debugowania nie istnieją dla metod interfejsu w Javie 7, a AFAIK ani w Javie 8.
Inne nowe funkcje to głównie nowe interfejsy API, ulepszenia HotSpot i narzędzia. Niektóre z nowych interfejsów API są dostępne jako biblioteki innych firm (np. ThreeTen-Backport i streamsupport ).
Summa summarum, metody domyślne wymagają nowych funkcji JVM, ale inne funkcje języka nie. Jeśli chcesz ich użyć, musisz skompilować kod w Javie 8, a następnie przekształcić kod bajtowy w formacie Retrolambda do formatu Java 5/6/7. Co najmniej wersja kodu bajtowego musi zostać zmieniona, a javac nie pozwala na to,
-source 1.8 -target 1.7
więc wymagany jest retrotranslator.źródło
O ile wiem, żadna z tych zmian w JDK 8 nie wymagała dodania nowych kodów bajtowych. Część instrumentacji lambda jest wykonywana przy użyciu
invokeDynamic
(które już istnieją w JDK 7). Tak więc z punktu widzenia zestawu instrukcji JVM nic nie powinno powodować niezgodności bazy kodu. Istnieje jednak wiele ulepszeń związanych z API i kompilatorów, które mogłyby utrudnić kompilację / uruchomienie kodu z JDK 8 w poprzednich JDK (ale nie próbowałem tego).Być może poniższy materiał referencyjny może w jakiś sposób pomóc w zrozumieniu, w jaki sposób są instrumentowane zmiany związane z lambdą.
Wyjaśniają one szczegółowo, w jaki sposób rzeczy są umieszczone pod maską. Być może znajdziesz tam odpowiedź na swoje pytania.
źródło
class C extends A with B
Jest zaimplementowany za pomocą normalnych interfejsówA
iB
klas towarzyszącychA$class
iB$class
. classC
po prostu przekazuje metody do statycznych klas towarzyszących. Typy własne nie są w ogóle wymuszane, wyrażenia lambda są transponowane w czasie kompilacji do abstrakcyjnych klas wewnętrznych, podobnie jaknew D with A with B
wyrażenie. Dopasowywanie wzorców to zbiór struktur if-else. Zwroty nielokalne? mechanizm try-catch z lambda. Zostało coś? (Co ciekawe, mój scalak mówi, że domyślnie jest 1,6)Jeśli chcesz użyć „retrotranslatora”, wypróbuj doskonałą Retrolambda firmy Esko Luontola: https://github.com/orfjackal/retrolambda
źródło
Możesz to zrobić,
-source 1.7 -target 1.7
a następnie się skompilujesz. Ale nie skompiluje się, jeśli masz funkcje specyficzne dla Java 8, takie jak lambdyźródło
-source 1.7
nie polecę.