Czy kod Java 8 można skompilować, aby działał na Java 7 JVM?

163

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?

Nicola Ambrosetti
źródło
możliwy duplikat Czy są jakieś konkretne przykłady wstecznej niezgodności między wersjami Java?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

146

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:

$ javac Test -source 1.8 -target 1.7
javac: source release 1.8 requires target release 1.8
JesperE
źródło
4
Nie, nie sądzę, żeby tak było. Java ma niewielki udział w rynku komputerów stacjonarnych, ale utrzymuje ten niewielki udział w dość ścisłej kontroli. Ale utrudnia to przyjmowanie nowych wersji i funkcji. Nie będę mógł używać funkcji Java 8 w kodzie, który piszę przez dłuższy czas, ponieważ chcę uniknąć konieczności aktualizacji lokalnej instalacji Javy.
JesperE
Czemu? „Tak” oznaczałoby, że Java 8 może zostać skompilowana w celu uruchomienia na maszynie wirtualnej Java 7, co jest niepoprawne według kompilatora Java 8.
JesperE
5
Teraz widzę: Twoje „Nie” odpowiada nagłówkowi pytania, a nie treści pytania.
Abdull
58

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.7więc wymagany jest retrotranslator.

Esko Luontola
źródło
3
W rzeczywistości adnotacje typu mogą być widoczne w czasie wykonywania. stackoverflow.com/questions/22374612/…
Antymon
33

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.

Edwin Dalorzo
źródło
7
Żadnych nowych kodów bajtowych, ale nowe struktury. Weryfikator zwymiotuje.
Jonathan S. Fisher
12
Dobrym przykładem są interfejsy. Mogą teraz zawierać metody. Weryfikator Java7 nie jest do tego przystosowany. Wszystkie stare kody bajtowe są używane, ale w nowy sposób.
Jonathan S. Fisher
1
Zastanawiam się, jak kompilator Scala z tak wieloma funkcjami językowymi może osiągnąć docelowe wydanie jvm nawet jdk5.
Marinos An
1
@MarinosAn Co dokładnie masz na myśli? MI z cechami, które zawierają konkretne metody, np. class C extends A with BJest zaimplementowany za pomocą normalnych interfejsów Ai Bklas towarzyszących A$classi B$class. class Cpo 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 jak new D with A with Bwyraż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)
Adowrath
1
Oczywiście typy własne itp. Są kodowane w specjalnych atrybutach klas i adnotacjach, dzięki czemu scalac może używać i egzekwować reguły podczas używania już skompilowanych klas.
Adowrath,
-5

Możesz to zrobić, -source 1.7 -target 1.7a następnie się skompilujesz. Ale nie skompiluje się, jeśli masz funkcje specyficzne dla Java 8, takie jak lambdy

kalgecin
źródło
Pytanie wyraźnie zakłada użycie nowych funkcji językowych, więc -source 1.7nie polecę.
toolforger