Можно ли скомпилировать код Java 8 для запуска на Java 7 JVM?
Java 8 представляет важные новые языковые функции, такие как лямбда-выражения.
Эти изменения в языке сопровождаются такими значительными изменениями в скомпилированном байт-коде, которые могут помешать его запуску на виртуальной машине Java 7 без использования какого-либо ретро-переводчика?
5 ответов
Нет, использование функций 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
Методы по умолчанию требуют таких изменений в байт-коде и 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
поэтому требуется ретротранслятор.
Насколько я знаю, ни одно из этих изменений в JDK 8 не требовало добавления новых байт-кодов. Часть лямбда-инструментария делается с использованием invokeDynamic
(которые уже существуют в JDK 7). Таким образом, с точки зрения набора инструкций JVM ничто не должно сделать кодовую базу несовместимой. Тем не менее, существует множество улучшений, связанных с API и компилятором, которые могут затруднить компиляцию / запуск кода из JDK 8 под предыдущими JDK (но я этого не пробовал).
Возможно, следующий справочный материал может помочь как-то обогатить понимание того, как проводятся изменения, связанные с лямбдой.
Они подробно объясняют, как все инструменты под капотом. Возможно, вы сможете найти ответ на свои вопросы там.
Если вы хотите использовать "ретротранслятор", попробуйте отличную Retrolambda от Esko Luontola: https://github.com/orfjackal/retrolambda
Ты можешь сделать -source 1.7 -target 1.7
тогда он скомпилируется. Но он не скомпилируется, если у вас есть специфические особенности Java 8, такие как лямбды