Можно ли скомпилировать код Java 8 для запуска на Java 7 JVM?

163

Java 8 представляет важные новые языковые функции, такие как лямбда-выражения.

Эти изменения в языке сопровождаются такими значительными изменениями в скомпилированном байт-коде, которые могут помешать его запуску на виртуальной машине Java 7 без использования какого-либо ретро-переводчика?

Никола Амбросетти
источник
Возможные дубликаты Есть ли конкретные примеры обратной несовместимости между версиями Java?
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

Ответы:

146

Нет, использование функций 1.8 в вашем исходном коде требует от вас целевой виртуальной машины 1.8. Я только что попробовал новую версию Java 8 и попытался скомпилировать -target 1.7 -source 1.8, и компилятор отказался:

$ javac Test -source 1.8 -target 1.7
javac: source release 1.8 requires target release 1.8
JesperE
источник
4
Нет, я не думаю, что так и будет. Java занимает небольшую долю на рынке настольных компьютеров, но удерживает эту небольшую долю в довольно жесткой власти. Но это затрудняет принятие новых версий и функций. Я не смогу использовать функции Java 8 в написанном мною коде в течение достаточно долгого времени, так как хочу избежать того, чтобы людям приходилось обновлять свою локальную установку Java.
JesperE
Зачем? «Да» означало бы, что Java 8 может быть скомпилирована для запуска на виртуальной машине Java 7, что неправильно в соответствии с компилятором Java 8.
JesperE
5
Теперь я вижу: ваше «Нет» отвечает на заголовок вопроса, а не на тело вопроса.
Абдул
58

Методы по умолчанию требуют таких изменений в байт-коде и JVM, которые было бы невозможно сделать в Java 7. Верификатор байт-кода в Java 7 и ниже отклонит интерфейсы с телами методов (за исключением метода статического инициализатора). Попытка эмулировать методы по умолчанию со статическими методами на стороне вызывающего не приведет к тем же результатам, потому что методы по умолчанию могут быть переопределены в подклассах. Retrolambda имеет ограниченную поддержку бэкпортинга методов по умолчанию, но никогда не может быть полностью перенесен обратно, потому что действительно требует новых функций JVM.

Lambdas мог бы работать на Java 7 как есть, если бы там просто существовали необходимые классы API. Инструкция invokedynamic существует в Java 7, но было бы возможно реализовать лямбда-выражения так, чтобы она генерировала лямбда-классы во время компиляции (ранние сборки JDK 8 делали это таким образом), и в этом случае она работала бы на любой версии Java. (Oracle решила использовать invokedynamic для лямбда-выражений для проверки будущего; возможно, однажды JVM будет иметь первоклассные функции, поэтому затем можно изменить invokedynamic, чтобы использовать их вместо генерации класса для каждой лямбды, что повысит производительность.) Что делает Retrolambda, так это что он обрабатывает все эти вызываемые динамические инструкции и заменяет их анонимными классами; так же, как то, что делает Java 8 во время выполнения, когда вызов lamdba вызывается впервые.

Повторение аннотаций - это просто синтаксический сахар. Они совместимы с предыдущими версиями. В Java 7 вам просто нужно реализовать вспомогательные методы (например, getAnnotationsByType ), которые скрывают детали реализации аннотации контейнера, которая содержит повторяющиеся аннотации.

AFAIK, аннотации типов существуют только во время компиляции, поэтому они не должны требовать изменения байт-кода, поэтому достаточно просто изменить номер версии байт-кода скомпилированных классов Java 8, чтобы они работали в Java 7.

Имена параметров метода существуют в байт-коде с Java 7, так что это также совместимо. Вы можете получить доступ к ним, читая байт-код метода и просматривая имена локальных переменных в отладочной информации метода. Например, Spring Framework делает именно это для реализации @PathVariable , поэтому, вероятно, есть библиотечный метод, который вы могли бы вызвать. Поскольку у абстрактных методов интерфейса нет тела метода, эта отладочная информация не существует для методов интерфейса в Java 7, а AFAIK - ни в Java 8.

Другие новые функции - это в основном новые API, улучшения HotSpot и инструментарий. Некоторые из новых API доступны в виде сторонних библиотек (например, ThreeTen- Backport и streamsupport ).

Summa summarum, методы по умолчанию требуют новых функций JVM, но другие языковые функции не делают. Если вы хотите их использовать, вам нужно скомпилировать код в Java 8, а затем преобразовать байт-код с Retrolambda в формат Java 5/6/7. Как минимум, версия байт-кода должна быть изменена, а javac запрещает -source 1.8 -target 1.7использование ретротранслятора.

Эско Луонтола
источник
3
На самом деле аннотации типов могут быть видны во время выполнения. stackoverflow.com/questions/22374612/…
Сурьма
33

Насколько я знаю, ни одно из этих изменений в JDK 8 не требовало добавления новых байт-кодов. Часть лямбда-инструментария выполняется с использованием invokeDynamic(которое уже существует в JDK 7). Таким образом, с точки зрения набора инструкций JVM ничто не должно сделать кодовую базу несовместимой. Тем не менее, существует множество улучшений, связанных с API и компилятором, которые могут затруднить компиляцию / запуск кода из JDK 8 под предыдущими JDK (но я этого не пробовал).

Может быть, следующий справочный материал может помочь как-то обогатить понимание того, как проводятся изменения, связанные с лямбдой.

Они подробно объясняют, как все инструменты под капотом. Возможно, вы сможете найти ответ на свои вопросы там.

Эдвин Далорсо
источник
7
Нет новых байт-кодов, но есть новые структуры. Верификатор будет рвать.
Джонатан С. Фишер
12
Хороший пример - интерфейсы. Теперь они могут содержать методы. Верификатор Java7 не оснащен для этого. Все старые байт-коды используются, но по-новому.
Джонатан С. Фишер
1
Интересно, как компилятор scala с таким количеством языковых функций может достичь целевой версии jvm даже jdk5.
Маринос
1
@MarinosAn Что именно вы имеете в виду? MI с чертами, которые содержат конкретные методы, например class C extends A with B, реализуется с обычными интерфейсами Aи Bи сопутствующими классами A$classи B$class. Класс Cпросто перенаправляет методы в статические сопутствующие классы. Самостоятельные типы вообще не применяются, лямбды передаются во время компиляции в абстрактные внутренние классы, как и new D with A with Bвыражения. Сопоставление с образцом - это связка структур if-else. Нелокальные возвращения? механизм try-catch от лямбды. Что-нибудь осталось? (Интересно, что мой скаляр говорит, что 1.6 по умолчанию)
Adowrath
1
Конечно, самотипы и т. Д. Закодированы в специальных атрибутах класса и аннотациях, чтобы скаляр мог использовать и применять правила при использовании уже скомпилированных классов.
Adowrath
-5

Вы можете сделать, -source 1.7 -target 1.7то он будет компилироваться. Но он не скомпилируется, если у вас есть специфические особенности Java 8, такие как лямбды

kalgecin
источник
Вопрос явно предполагает использование новых возможностей языка, поэтому -source 1.7не будет летать.
toolforger