Среди функций - новый API для замены Java Native Interface (JNI) и улучшения в выражениях и операторах switch (предварительная версия).

JDK 17 - это версия с долгосрочной поддержкой (LTS), что означает поддержку Oracle на многие годы вперед. Другими пакетами LTS JDK являются Java 8 и Java 11. Пакеты LTS JDK выпускаются каждые три года, а не LTS - каждые шесть месяцев.

Для сравнения: поддержка версий LTS длится несколько лет, в то время как поддержка JDK без LTS длится всего шесть месяцев и заканчивается, когда выходит следующая версия, отличная от LTS.

Вот подробности четырнадцати предложений по усовершенствованию JDK (JEP), разделенных новым, удаленным и предварительным просмотром.

Новый

Он также включает улучшения и доработанные функции, такие как запечатанные классы.

JEP 391: порт macOS / AArch64

В 2020 году Apple объявила о двухлетнем плане перехода (Apple Silicon) по переводу своего центрального процессора (ЦП) с процессоров Intel x86–64 на разработанные Apple микросхемы, использующие архитектуру ARM64 / AArch64.

JEP 391 посвящен переносу JDK на новую архитектуру.

Процессоры на базе Arm всегда рассматривались как нацеленные на рынок встраиваемых систем и мобильных устройств. Они обеспечивают достаточную производительность при низком потреблении энергии. Но это меняется. Некоторые производители оборудования сейчас используют 64-битную архитектуру Arm, называемую AArch64, чтобы конкурировать с архитектурой x86–64.

Хотя можно будет запустить сборку JDK Intel x86–64 для macOS на новых машинах на базе AArch64, перевод, несомненно, приведет к значительному снижению производительности.

Подробнее здесь.

JEP 409: Запечатанные классы

Запечатанные классы не новость. Впервые она была предложена в Java 15 в качестве функции предварительного просмотра и снова предложена с некоторыми настройками в Java 16. Кроме того, в качестве функции предварительного просмотра.

Теперь он доработан на Java 17 без отличий от Java 16.

Запечатанные классы или интерфейсы могут ограничивать, какие классы или интерфейсы могут их расширять или реализовывать. Это делается с помощью sealed modifier, как в примере ниже.

public abstract sealed class Shape
    permits Circle, Rectangle, Square { ... }

Можно ограничить подклассы, сделав класс окончательным или используя конструктор package-private. Но у таких подходов есть свои ограничения.

Разработчики Java знакомы с идеей ограничения набора подклассов, потому что она часто возникает при проектировании API. Язык предоставляет ограниченные инструменты в этой области: либо создайте класс final, чтобы он не имел подклассов, либо сделайте класс или его конструктор пакетным, чтобы он мог иметь подклассы только в одном пакете. Пример суперкласса package-private появляется в JDK:

package java.lang;

abstract class AbstractStringBuilder { ... }
public final class StringBuffer extends AbstractStringBuilder { ... }
public final class StringBuilder extends AbstractStringBuilder { ... }

Подход частный пакет полезен, когда целью является повторное использование кода, например, когда подклассы AbstractStringBuilder совместно используют его код для append. Однако этот подход бесполезен, когда целью является моделирование альтернатив, поскольку пользовательский код не может получить доступ к ключевой абстракции - суперклассу - для того, чтобы switch над ним.

Подробнее о закрытых классах можно прочитать здесь.

JEP 382: новый конвейер рендеринга macOS

JEP 382 реализует новый конвейер 2D-рендеринга Java для macOS с использованием новой инфраструктуры Apple Metal.

Платформа Metal заменяет библиотеку рендеринга OpenGL, которую Apple исключила в macOS 10.14 (сентябрь 2018 г.).

Есть риск, что Apple удалит OpenGL API из будущей версии macOS. Следовательно, Java 2D должна быть готова к совместимости с каркасом Metal. Сегодня Java 2D полностью зависит от OpenGL.

Apple утверждает, что с точки зрения производительности каркас Metal намного превосходит его. Для Java 2D это так, за некоторыми исключениями.

Подробнее здесь.

JEP 412: API внешних функций и памяти (инкубатор)

JDK 17 представляет гораздо более простой способ интеграции с кодом и данными вне среды выполнения Java - API внешней функции и памяти (FFM).

Как описано в JEP, он заменяет собственный интерфейс Java (JNI) более совершенной моделью разработки на чистом Java.

Вот пример с сайта OpenJDK.

// 1. Find foreign function on the C library path
MethodHandle radixSort = CLinker.getInstance().downcallHandle(
                           CLinker.systemLookup().lookup("radixsort"), ...);
// 2. Allocate on-heap memory to store four strings
String[] javaStrings   = { "mouse", "cat", "dog", "car" };
// 3. Allocate off-heap memory to store four pointers
MemorySegment offHeap  = MemorySegment.allocateNative(
                             MemoryLayout.ofSequence(javaStrings.length,
                                                     CLinker.C_POINTER), ...);
// 4. Copy the strings from on-heap to off-heap
for (int i = 0; i < javaStrings.length; i++) {
    // Allocate a string off-heap, then store a pointer to it
    MemorySegment cString = CLinker.toCString(javaStrings[i], newImplicitScope());
    MemoryAccess.setAddressAtIndex(offHeap, i, cString.address());
}
// 5. Sort the off-heap data by calling the foreign function
radixSort.invoke(offHeap.address(), javaStrings.length, MemoryAddress.NULL, '\0');
// 6. Copy the (reordered) strings from off-heap to on-heap
for (int i = 0; i < javaStrings.length; i++) {
    MemoryAddress cStringPtr = MemoryAccess.getAddressAtIndex(offHeap, i);
    javaStrings[i] = CLinker.toJavaStringRestricted(cStringPtr);
}
assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"});  // true

Подробнее здесь.

JEP 414: Vector API (второй инкубатор)

В Java 17 внесены усовершенствования в уже предложенную часть Vector API Java 16. Усовершенствования являются ответом на отзывы, и вот основные изменения:

Улучшения API для поддержки операций с символами, например для декодирования символов UTF-8. В частности, мы добавляем методы для копирования символов между векторами short и char массивами, а также новые операторы сравнения векторов для беззнаковых сравнений с целыми векторами.

Улучшения API для преобразования векторов byte в массивы boolean и обратно.

Встроенная поддержка трансцендентных и тригонометрических операций с каналом на x64 с использованием библиотеки Intel Short Vector Math Library (SVML).

Общие улучшения производительности для реализаций Intel x64 и ARM NEON.

Подробнее здесь.

JEP 356: Расширенные генераторы псевдослучайных чисел

JEP 356 представляет новые типы интерфейсов и реализацию для генераторов псевдослучайных чисел (PRNG).

Цели состоят в том, чтобы упростить переключение между различными алгоритмами PRGN, удалить дублированный код в существующих PRGN, обеспечить лучшую поддержку потокового программирования и, наконец, сохранить текущее поведение java.util.Random.

Подробнее здесь.

JEP 306: Восстановить всегда строгую семантику с плавающей запятой

Это делает операции с плавающей запятой всегда строгими. Два варианта: строгий и по умолчанию, не существуют в JDK 17.

Этот JEP упрощает разработку важных библиотек, таких как java.lang.Math и java.lang.StrictMath. Это также придает большую регулярность этому аспекту платформы.

Как объяснялось подробно, семантика с плавающей запятой по умолчанию была создана в Java 1.2. Это устраняет большие накладные расходы при точном сопоставлении с плавающей запятой во всех случаях. Накладные расходы возникли из-за плохого взаимодействия между исходной Java и некоторыми досадными особенностями x87.

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

JEP 403: строго инкапсулируйте внутренние компоненты JDK

JDK 17 не позволяет ослабить сильную инкапсуляцию с параметром запуска-нелегальный -доступ.

JEP 396 перешел от ослабленной сильной инкапсуляции по умолчанию к строгой инкапсуляции по умолчанию, но в JDK с 9 по 16 можно ослабить ее. В JDK 17 использование опции запуска больше невозможно.

Примеры влияния этого изменения

Код, успешно скомпилированный с более ранними выпусками, которые напрямую обращаются к внутренним API JDK, больше не будет работать. Например,

System.out.println(sun.security.util.SecurityConstants.ALL_PERMISSION);

потерпит неудачу за исключением формы

Exception in thread "main" java.lang.IllegalAccessError: class Test (in unnamed module @0x5e481248) cannot access class sun.security.util.SecurityConstants (in module java.base) because module java.base does not export sun.security.util to unnamed module @0x5e481248

Код, использующий отражение для доступа к полям private экспортированных java.* API, больше не будет работать. Например,

var ks = java.security.KeyStore.getInstance("jceks"); var f = ks.getClass().getDeclaredField("keyStoreSpi"); f.setAccessible(true);

потерпит неудачу за исключением формы

Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private java.security.KeyStoreSpi java.security.KeyStore.keyStoreSpi accessible: module java.base does not "opens java.security" to unnamed module @6e2c634b

Код, использующий отражение для вызова методов protected экспортированных java.* API, больше не будет работать. Например,

var dc = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); dc.setAccessible(true);

потерпит неудачу за исключением формы

Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @5e481248

JEP 415: контекстно-зависимые фильтры десериализации

Это усовершенствование фильтров десериализации (JEP 290), представленных в Java 9.

Фильтры добавляют уровень безопасности при десериализации данных. Он проверяет данные перед их десериализацией.

Разница между Java 9 и 17 заключается в том, что в JDK 17 фильтры могут быть контекстно-зависимыми и динамически определяться разработчиком.

Подробнее здесь.

Как предварительный просмотр

JEP 406: сопоставление с образцом для переключателя (предварительный просмотр)

Как указано здесь, этот JEP расширяет выразительность и применимость выражений и операторов switch.

Вот цели:

Расширьте выразительность и применимость выражений и утверждений switch, разрешив появление шаблонов в case ярлыках.

При желании позвольте ослабить историческую нулевую враждебность switch.

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

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

Не вводите новое switch -подобное выражение или оператор с семантикой сопоставления с образцом, отличной от традиционной конструкции switch.

Не заставляйте выражение или оператор switch вести себя по-разному, когда метки case являются шаблонами, и когда метки case являются традиционными константами.

Пример текущего и предлагаемого использования переключателя.

Текущее:

static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

Предлагается:

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
}

А ниже показано, как в коммутатор интегрирована обработка нулевых значений.

static void testFooBar(String s) {
    switch (s) {
        case null         -> System.out.println("Oops");
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

Удалено / устарело для удаления

JEP 398: исключить API-интерфейс апплета для удаления

В JDK 17 API апплета помечен как устаревший для удаления. Это означает, что JDK 18 удалит API.

Удаление Applet API давно назрело, учитывая, что все браузеры либо прекратили поддержку, либо планируют это сделать. API уже устарел в JDK 9, но не был помечен для удаления.

API, помеченные как устаревшие для удаления, означают, что API будет удален в следующем выпуске. Устаревшие для удаления - это определение, действующее в JDK 9. До этого API-интерфейсы были помечены как устаревшие, но никогда не удалялись.

Приложения, использующие API апплета, должны начать планировать миграцию.

Подробнее здесь.

JEP 411: отказ от диспетчера безопасности для удаления

Наряду с API Java-апплета, Менеджер безопасности (SM) также устарел для удаления. SM присутствует с Java 1.0, когда веб-браузеры загружали java-апплеты. SM защитил компьютеры и данные пользователей, изолировав апплет от файловой системы или сети.

Но поскольку API Java-апплета не рекомендуется для удаления, защита, предлагаемая Security Manager, становится несущественной. Да и внедрение Security Manager всегда было на низком уровне.

Подробнее здесь.

JEP 407: Удалить активацию RMI

Активация удаленного вызова метода (RMI) эффективно удаляется. Он объявлен устаревшим для удаления JEP 385 в Java SE 15.

После обширных исследований на популярных форумах, таких как StackOverflow и JavaRanch, а также на базе кода с открытым исходным кодом, есть явные признаки того, что активация RMI используется значительно меньше.

Сохранение его как части платформы Java требует больших затрат на обслуживание. Активация RMI была необязательной, начиная с Java 8.

Вот мотивация OpenJDK.

Механизм активации RMI устарел и не используется. Он объявлен устаревшим для удаления JEP 385 в Java SE 15. Никаких комментариев не было получено в ответ на эту отмену. Пожалуйста, см. JEP 385 для получения полной информации, обоснования, рисков и альтернатив.

Платформа Java EE содержала технологию под названием JavaBeans Activation Framework (JAF). Позже он был переименован в Jakarta Activation в рамках инициативы Eclipse EE4J. Технологии активации JavaBeans и Jakarta Activation полностью не связаны с активацией RMI, и на них не влияет удаление активации RMI из Java SE.

Активация RMI - интересная функция. Он создает экземпляры удаленных объектов по запросу, что упрощает разработку, поскольку нет необходимости обрабатывать недопустимые заглушки. Тем не менее, он мало использовался.

JEP 410: удалить экспериментальный компилятор AOT и JIT

JDK 17 удаляет экспериментальный Java-компилятор с опережением времени (AOT) и JIT-компилятором. Сборки JDK 16, опубликованные Oracle, уже не включали их.

AOT и JIT были введены в JDK 9 и 10, но от них было мало пользы. И это требует значительных затрат на обслуживание. Вот список изменений:

Удалите три модуля JDK:

jdk.aot - инструмент jaotc

jdk.internal.vm.compiler - компилятор Грааля

jdk.internal.vm.compiler.management - MBean Грааля

Сохраните эти два исходных файла, связанных с Graal, чтобы модуль JVMCI (jdk.internal.vm.ci, JEP 243) продолжал сборку:

src/jdk.internal.vm.compiler/share/classes/module-info.java

src/jdk.internal.vm.compiler.management/share/classes/module-info.java

Удалите код HotSpot, связанный с компиляцией AOT:

src/hotspot/share/aot - выгружает и загружает код AOT

Дополнительный код охраняется #if INCLUDE_AOT

Наконец, удалите тесты, а также код из make-файлов, связанных с компиляцией Graal и AOT.

Последние мысли

Пора было уйти с Applet, включая Security Manager, а Java должна идти в ногу с последними обновлениями Apple - в этом нет ничего удивительного.

JEP 356 значительно улучшит общее состояние реализации, связанной с ГПСЧ, сделав ее более простой в использовании.

Интересен новый API «FFM API». Из этого примера видно, что доступ к коду и данным вне среды выполнения Java намного проще, чем при использовании собственного интерфейса Java (JNI).

Мне также понравились усовершенствования в выражениях и утверждениях «переключения».

Что вы думаете? Дай мне знать в комментариях. Спасибо за прочтение.

Для дальнейшего чтения