Теория и практика Java: Использование возможностей языка Java 5 в предыдущих версиях JDK

Даже если вы привязаны к JDK 1.4, вы можете использовать generic'и

В версии Java™ 5 в язык было добавлено много значительных возможностей: generic'и, перечисляемые типы, аннотации, autoboxing, улучшенный цикл for. Однако многие группы разработки все еще привязаны к JDK 1.4 или более ранним версиям и могут находиться в таком состоянии еще некоторое время. Тем не менее эти разработчики все-таки могут использовать эти полезные возможности языка, продолжая устанавливать приложения на ранние версии JVM. Возвратившийся после перерыва Брайан Гетц в этой статье серии Теория и практика Java покажет, как этого добиться.

Брайан Гетц, главный консультант, Quiotix

Брайан Гетц (Brian Goetz) последние 15 лет работает профессиональным разработчиком ПО. Сейчас он является главным консультантом в фирме Quiotix, занимающейся разработкой ПО и консалтингом и находящейся в Лос-Альтос, Калифорния. Он также является членом нескольких экспертных групп JCP (JCP Expert Groups). См. его опубликованные и готовые к публикации статьи в популярных отраслевых изданиях. Вы можете связаться с ним по адресу brian@quiotix.com.



28.11.2007

После недавнего выхода Java 6.0 можно было бы подумать, что возможности Java 5 уже стали стандартом. Но даже сейчас, когда я спрашиваю программистов, какую версию платформы Java они используют для разработки, обычно только половина применяет Java 5, а другая половина им завидует. Многие хотели бы использовать возможности языка, добавленные в Java 5, такие как generic'и и аннотации, но в силу определенных причин не могут сделать этого.

Одна из категорий разработчиков, которые не могут воспользоваться преимуществами функциональности Java 5, - это разработчики компонентов, библиотек и каркасов для разработки приложений. Дело в том, что их заказчики, возможно, все еще используют JDK 1.4 или более ранние версии, а классы, скомпилированные под Java 5, не могут быть загружены JDK 1.4 и более старыми JVM. Поэтому использование возможностей языка Java 5 может привести к сокращению круга их заказчиков, оставив им только компании, уже перешедшие на Java 5.

Другая группа разработчиков, воздерживающаяся от использования Java 5, - это программисты, работающие с Java EE. Многие группы разработчиков не хотят использовать Java 5 c Java EE 1.4 и более ранними версиями из опасения, что Java 5 не будет поддерживаться производителем используемого ими сервера приложений. Так что пройдет некоторое время, прежде чем такие проекты начнут переходить на Java 5. Также, кроме временного разрыва между спецификациями Java EE 5 и Java SE 5, не факт, что коммерческие контейнеры Java EE 5 появятся сразу же после выпуска новой версии спецификации. Компаниям вовсе не обязательно обновлять свои сервера приложений сразу после выхода новой версии, и даже после обновления сервера приложений может потребоваться время для сертификации приложений на новую платформу.

Реализация возможностей языка в Java 5

Возможности языка, добавленные в Java 5, - generic'и, перечисляемые типы, аннотации, autoboxing и улучшенный цикл for- не требуют изменений в наборе инструкций JVM и практически полностью реализованы в статическом компиляторе javac и библиотеках классов. Когда компилятор обнаруживает использование generic'ов, он пытается проверить, что обеспечивается безопасность типов, выдавая предупреждение "unchecked cast" (непроверенное преобразование), если не может это проверить, а затем генерирует байт код, полностью идентичный байт-коду, полученному из эквивалентного не-generic кода с преобразованиями и т.д. Точно также autoboxing и улучшенный цикл for - это просто "синтаксическая лафа" для совершенно эквивалентного, но более длинного кода, а перечисляемые типы компилируются в обычные классы.

В теории можно взять классы, сгенерированные javac, и загрузить их в более раннюю JVM. На самом деле это и было целью создания группы JSR 14, группы Java Community Process, отвечающей за generic'и. Однако другие проблемы (такие как сохранение аннотаций), заставили изменить версию файла класса в Java 5 по сравнению с Java 1.4, что не позволяет загружать код, скомпилированный для Java 5, в более ранние версии JVM. Также некоторые из возможностей языка, добавленные в Java 5, зависят от библиотек Java 5. Если откомпилировать класс командой javac -target 1.5 и попытаться загрузить его в более раннюю JVM, возникнет ошибка UnsupportedClassVersionError, так как параметр -target 1.5 генерирует классы с версией файла класса равной 49, а JDK 1.4 поддерживает только версии файлов классов до 48.

Цикл for-each

Улучшенный цикл for, иногда называемый циклом for-each, обрабатывается компилятором так, как если бы программист предоставил аналогичный цикл for в старом стиле. Цикл for-each позволяет осуществлять итерацию по элементам массива или коллекции. В примере 1 приведен синтаксис итерации по коллекции с помощью цикла for-each.

Пример 1. Цикл for-each
Collection<Foo> fooCollection = ...

for (Foo f : fooCollection) { 
    doSomething(f);
}

Компилятор превратит этот код в аналогичный цикл, использующий итераторы, как показано в примере 2.

Пример 2. Эквивалент кода в примере 1 с использованием итераторов
for (Iterator<Foo> iter=f.iterator(); f.hasNext();) { 
    Foo f = (Foo)iter.next();
    doSomething(f);
}

Каким образом компилятор определяет, что у предоставленного аргумента есть метод iteraror()? Архитекторы компилятора javac могли бы встроить в него понимание framework'a для работы с коллекциями, но этот подход мог привести к ненужным ограничениям. Вместо этого был создан новый интерфейс java.lang.Iterable (см. пример 3), а классы коллекций были модифицированы так, чтобы реализовывать Iterable. В результате классы-контейнеры, даже не построенные на базовых коллекциях из framework'a, также могут использовать возможности нового цикла for-each. Но это создает зависимость от библиотеки классов Java 5, так как интерфейс Iterable отсутствует в библиотеке JDK 1.4.

Пример 3. Интерфейс Iterable
public interface Iterable<T> {
    Iterator<T> iterator();
}

Перечисляемые типы и autoboxing

Также как и цикл for-each, перечисляемые типы требуют поддержки библиотеки классов. Когда компилятор встречает перечисляемый тип, он генерирует класс, наследующий библиотечному классу java.lang.Enum. Но также как и Iterable, класс Enum отсутствует в библиотеке классов JDK 1.4.

Аналогично autoboxing использует методы valueOf(), добавленные в классы-оболочки для примитивных типов, такие как Integer. Если boxing требует преобразования из int в Integer, то вместо вызова new Integer(int) компилятор генерирует вызов Integer.valueOf(int). Эта реализация метода valueOf() использует шаблон flyweight для кэширования объектов Integer для часто используемых целых значений типа integer. Например, реализация Java 6 кэширует integer значения от -128 до 127, что позволяет производительность за счет устранения избыточного создания объектов. При этом, как и в случае Iterable и Enum, метод valueOf() отсутствует в библиотеке классов JDK 1.4.

Varargs (методы с переменным числом аргументов)

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

Аннотации

При определении аннотации она может быть объявлена с аннотацией @Retention, которая указывает, как компилятор должен поступать с классами, методами или полями, которые используют эту аннотацию. Предусмотренные политики сохранения - SOURCE (отбрасывать данные из аннотации при компиляции), CLASS (записывать аннотации в файл класса), RUNTIME (записывать аннотации в файл класса и сохранять их во время исполнения, чтобы к ним можно было обращаться рефлексивным образом).

Другие зависимости от библиотек

До Java 5, когда компилятор встречал попытку сложить две строки, он использовал вспомогательный класс StringBuffer, чтобы выполнить сложение. В Java 5 и более поздних версиях вместо этого он генерирует вызовы к новому классу StringBuilder, который не представлен в JDK 1.4 и более ранних библиотеках классов.


Использование возможностей Java 5

Из-за зависимости возможностей языка от библиотек поддержки даже если class-файлы, созданные компилятором Java 5, удастся загрузить в предыдущую версию JVM, то исполнить их все равно не получится из-за ошибок при загрузке классов. Однако подобные проблемы можно решить соответствующим преобразованием байт-кода, так как недостающие классы не содержат важной функциональности.

JSR 14

Во время разработки спецификации Java generic'ов и других возможностей языка Java, добавленных в Java 5, в компилятор javac была добавлена экспериментальная поддержка, позволяющая ему использовать возможности языка Java 5 и генерировать байт-код, который можно запускать на JVM 1.4. Хотя эти возможности официально не поддерживаются и даже не задокументированы, они используются в ряде проектов Open Source, чтобы позволить разработчикам использовать языковые возможности Java 5 и создавать JAR-файлы, работающие на более ранних JVM. Теперь, когда javac стал Open Source-проектом, эти возможности могут поддерживаться сторонними разработчиками. Чтобы активировать эти возможности, можно запустить javac с параметрами -source 1.5 и -target jsr14.

Режим JSR 14 заставляет компилятор javac генерировать JDK 1.4-совместимый байт-код, обладающий возможностями Java 5.

  • Generics и varargs: Преобразования, вводимые компилятором при наличии generic'ов, не зависят от библиотек классов, так что они могут так же успешно исполняться на предшествующих JVM. Точно так же код, генерируемый компилятором при наличии списка аргументов переменной длины, не зависит от библиотеки классов.
  • Цикл for-each: При итерации по массиву компилятор генерирует индукционную переменную и стандартный код для итерации по массиву. Когда выполняется итерация по объекту Collection, компилятор генерирует стандартный код на основе итератора. При итерации по объекту Iterable, не принадлежащему к типу Collection, компилятор выводит ошибку.
  • Autoboxing: Вместо генерации вызова метода valueOf() в классе-оболочке компилятор генерирует вызов конструктора.
  • Сложение строк: Режим JSR 14 заставляет компилятор javac генерировать вызовы к StringBuffer вместо StringBuilder.
  • Перечисляемые типы: Перечисляемые типы. Режим JSR 14 компилятора javac не предоставляет специальной поддержки перечисляемых типов. Код, пытающийся использовать перечисляемые типы, вызовет сбой с ошибкой NoClassDefFoundError при поиске базового класса java.lang.Enum.

Использование режима JSR 14 позволяет писать код, использующий generic'и, autoboxing и цикл for-each в легких случаях, которых достаточно для большинства проектов. Это удобная, хотя и не поддерживаемая, возможность; при этом компилятор генерирует максимально совместимый байт-код за один проход.

Retroweaver

В Java 5 есть языковые возможности, не поддерживаемые режимом JSR 14, например, такие как Iterable и перечисляемые типы. Альтернативный подход, применяемый в таких Open Source-проектах, как Retroweaver и Retrotranslator, предлагает генерировать байт-код, используя параметр -target 1.5, и затем механически преобразовывать байт-код в формат, совместимый с JDK 1.4

Первой появилась утилита Retroweaver, которая обрабатывает все случаи, обрабатываемые javac -target JSR 14, а также предоставляет еще несколько возможностей.

  • Цикл for-each: Retroweaver предлагает реализацию интерфейса Iterable и переписывает классы, реализующие Iterable в соответствии со своей собственной версией реализации.
  • Autoboxing: Retroweaver заменяет вызовы к valueOf() на соответствующие конструкторы.
  • Сложение строк: Retroweaver заменяет использование класса StringBuilder на использование класса StringBuffer.
  • Перечисляемые типы: Retroweaver предоставляет реализацию базового класса Enum и переписывает классы, реализующие Enum или использующие его методы таким образом, чтобы они использовали версию Enum из Retroweaver.

Retrotranslator

Как это часто случается в Open Source-сообществе, если проект перестает двигаться вперед, он объявляется "мертвым", и его место занимает новый проект, даже если первый проект только решил "отдохнуть". Именно такая история произошла с Retroweaver - основной разработчик проекта решил отдохнуть от него, и его место занял другой аналогичный проект - Retrotranslator. Retrotranslator предлагает такие же возможности, как Retroweaver, и еще много дополнительных возможностей, направленных на поддержку важных обновлений в библиотеке классов Java 5.

  • Заменяет вызовы классов java.util.concurrent вызовами соответствующих классов Open Source-реализации обратной совместимости с JDK 1.4.
  • Предлагает реализацию возможностей, добавленных в Collections framework в Java 5, таких как Arrays и Collections. Аналогично предлагает реализации других новых методов и классов, добавленных в библиотеку классов Java 5.
  • Поддерживает использование Reflections API для аннотаций во время исполнения программы.

И Retroweaver, и Retrotranslator могут выполнять трансформацию байт-кода как статически (во время компиляции), так и динамически (во время загрузки классов).


Заключение

У тех невезучих разработчиков, которые не могут использовать новые возможности Java 5, - а таких разработчиков, к сожалению, еще очень много, - есть несколько путей, позволяющих им использовать новые возможности и сохранить совместимость байт-кода с JDK 1.4 и более старыми версиями. Имеется неподдерживаемая опция -target jsr14 компилятора javac, позволяющая генерировать JDK 1.4-совместимый байт-код для некоторых возможностей Java 5, а также Open Source-проекты Retroweaver и Retrotranslator, преобразующие большинство байт-кода Java 5 в Java 1.4-совместимый байт-код. Что бы вы ни выбрали, не забудьте выполнить тестирование с особой тщательностью, чтобы убедиться в полной совместимости.

Ресурсы

Научиться

Получить продукты и технологии

  • Загрузите Retroweaver: воспользуйтесь новыми возможностями Java 1.5, сохранив полную бинарную совместимость с виртуальными машинами версии 1.4.(EN)
  • Загрузите Retrotranslator: Open Source-инструмент, преобразующий Java классы, скомпилированные для JDK 5, в классы, которые могут быть запущены на JVM 1.4(EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=271570
ArticleTitle=Теория и практика Java: Использование возможностей языка Java 5 в предыдущих версиях JDK
publish-date=11282007