Вышла Java 16
Вышла 16-я версия платформы Java SE. В этот релиз попало около двух с половиной тысяч закрытых задач и 17 JEP’ов. Изменения API можно посмотреть здесь. Release notes здесь.
Уже сейчас доступны для скачивания дистрибутивы Oracle JDK и OpenJDK.
JEP’ы, которые попали в Java 16, мы разобьём на четыре категории: язык, API, JVM и инфраструктура.
Язык
Паттерн-матчинг для оператора instanceof (JEP 375)
Оператор instanceof с паттерн-матчингом, который появился в Java 14 и перешёл во второе preview в Java 15, теперь стал стабильной синтаксической конструкцией и больше не требует флага —enable-preview . Паттерн-матчинг мы подробно рассматривали в этой статье, и с того момента в него было внесено два изменения:
Во-первых, переменные паттернов теперь не являются неявно финальными:
if (obj instanceof String s) < s = "Hello"; // OK в Java 16, ошибка в Java 15 >
Во-вторых, если тип выражения, известный на этапе компиляции, является подтипом проверяемого типа, то теперь это ошибка компиляции:
String str = . if (str instanceof String s) < // Oшибка в Java 16, OK в Java 15 >
Записи (JEP 395)
Ещё одна синтаксическая конструкция, которая стала стабильной – это записи. Она также была в режиме preview в Java 14 и Java 15. Записи мы также подробно рассматривали ранее. В Java 16 было внесено следующее изменение: теперь во внутренних классах разрешено объявлять статические члены:
public class Outer < public class Inner < // OK в Java 16, ошибка в Java 15 static void main(String[] args) < >// OK в Java 16, ошибка в Java 15 record Point(int x, int y) < >> >
sealed классы (второе preview) (JEP 397)
«Запечатанные» классы, которые появились в Java 15 в режиме preview, остаются в этом статусе. Их мы рассматривали в этой статье. Изменения по сравнению с прошлой версией следующие:
- Теперь в спецификации языка Java появилось понятие contextual keyword взамен старым понятиям restricted keyword и restricted identifier, и одними из таких contextual keywords стали sealed , non-sealed и permits .
- Компилятор теперь производит более строгие проверки при конверсии типов, в иерархиях которых есть sealed классы:
sealed interface Sealed < >final class Impl implements Sealed < void f(Runnable r) < Sealed s = (Sealed) r; // error: incompatible types >>
JVM
Строгая инкапсуляция внутренностей JDK по умолчанию (JEP 396)
Инкапсуляция внутренних API JDK, которая была введена в Java 9, теперь стала строгой: если в Java 9-15 значение опции —illegal-access было по умолчанию permit , то с Java 16 она становится deny . Это значит, что рефлективный доступ к защищённым членам классов и статический доступ к неэкспортированным API ( sun.* , com.sun.* , jdk.internal.* и т.д.) теперь будет выбрасывать ошибку.
Если код требует доступа к внутренностям JDK во время выполнения, то чтобы он продолжал работать на Java 16, теперь придётся явно указывать одну из трёх опций JVM:
- —illegal-access=permit/warn/debug : открытие всех пакетов JDK
- —add-opens=module/package=target-module : открытие одного пакета
- —add-exports=module/package=target-module : экспортирование одного пакета (только для статического доступа)
В будущем опция —illegal-access может быть удалена окончательно. Начиная с Java 16, при её использовании выдаётся предупреждение: Option —illegal-access is deprecated and will be removed in a future release .
Изменения не касаются критического API в модуле jdk.unsupported : классы в пакетах sun.misc и sun.reflect остаются доступными без флагов.
Warnings for Value-Based Classes (JEP 390)
Классы-обёртки примитивных типов ( Integer , Double , Character и т.д.) теперь относятся к категории value-based классов, и их конструкторы, которые ранее стали deprecated в Java 9, теперь помечены как deprecated for removal.
Понятие value-based классов появилось в спецификации API Java 8. Такие классы являются неизменяемыми, создаются только через фабрики, и в их использовании не должны использоваться операции, чувствительные к identity: сравнение на == , синхронизация, identityHashCode() и т.д. Value-based классы являются кандидатами для миграции на примитивные классы в рамках проекта Valhalla, который сейчас находится в стадии активной разработки.
При синхронизации на объектах value-based классов теперь будет выдаваться предупреждение во время компиляции:
Double d = 0.0; synchronized (d) < // warning: [synchronization] attempt to synchronize on an instance of a value-based class >
Также можно включить проверки синхронизации на value-based объектах во время выполнения с помощью флагов JVM:
- -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=1 : при попытке синхронизации будет фатальная ошибка.
- -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2 : при попытке синхронизации будет предупреждение.
ZGC: Concurrent Thread-Stack Processing (JEP 376)
Обработка стеков потоков в сборщике мусора ZGC теперь перенесена из safepoints в конкурентную фазу. Это позволило ещё сильнее уменьшить паузы сборщика мусора.
Unix-Domain Socket Channels (JEP 380)
Добавлена поддержка сокетов доменов Unix в socket channel и server-socket channel API. Такие сокеты используются для межпроцессного взаимодействия внутри одного хоста, и в них не используются сетевые соединения, что делает такое взаимодействие более безопасным и эффективным. Сокеты доменов Unix с недавних пор поддерживаются в Windows 10 и Windows Server 2019.
Elastic Metaspace (JEP 387)
Metaspace (пространство JVM, в котором хранятся метаданные классов) переработан для более эффективной отдачи неиспользуемой памяти обратно операционной системе и меньшего потребления памяти вне кучи в целом. Такое улучшение может быть полезно для приложений, которые интенсивно загружают и выгружают классы посредством большого количества загрузчиков классов.
Alpine Linux Port (JEP 386)
JDK теперь портирован на Alpine Linux и другие дистрибутивы Linux, которые используют musl в качестве реализации стандартной библиотеки C. Alpine Linux популярен в облаках, микросервисах и контейнерах благодаря своему маленькому размеру образа. Новый порт позволит нативно запускать JDK в этих окружениях.
Windows/AArch64 Port (JEP 388)
JDK также портирован на архитектуру Windows/AArch64. Это позволит запускать Java на компьютерах с Windows on ARM, которые в последнее время набирают популярность.
API
Новые методы в Stream
Хотя для этих двух новых методов в интерфейсе java.util.stream.Stream нет отдельного JEP, хочется упомянуть их здесь, так как это довольно заметное изменение.
Первый метод – это Stream.toList() . Этот метод собирает содержимое Stream в неизменяемый список и возвращает его. При этом, в отличие от Collectors.toUnmodifiableList() , список, который возвращается из Stream.toList() , толерантен к null -элементам.
Второй метод – это Stream.mapMulti() (и примитивные специализации). Это метод является императивным аналогом метода Stream.flatMap() : если flatMap() принимает функцию, которая для каждого элемента должна вернуть Stream , то mapMulti() принимает процедуру с двумя параметрами, где первый параметр – это текущий элемент, а второй – Consumer, в который кладутся значения. Пример:
IntStream.rangeClosed(1, 10).mapMulti((i, consumer) -> < for (int j = 1; j >); // Возвращает 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, .
Инструмент упаковки (JEP 392)
Инструмент создания самодостаточных приложений jpackage , который появился в Java 14 в инкубационном статусе, теперь стал постоянным модулем.
Vector API (Incubator) (JEP 338)
Появился новый инструментарий для преобразования векторных вычислений в SIMD-инструкции процессора (x64 и AArch64). Векторное API позволит разработчику контролировать процесс компиляции и не полагаться на автовекторизацию, которая в JVM является ограниченным и хрупким механизмом. Явная векторизация может применяться в таких областях как машинное обучение, линейная алгебра, криптография и др.
API находится в инкубационном модуле jdk.incubator.vector .
Foreign Linker API (Incubator) (JEP 389)
Ещё одно новое API, которое появилось в результате работы над проектом Panama – это Foreign Linker API. Это инструментарий для статического доступа к нативному коду из Java, созданный для замены JNI: он должен быть более простым в использовании, более безопасным и желательно более быстрым.
Про Foreign API делал доклад Владимир Иванов из Oracle.
Foreign-Memory Access API (Third Incubator) (JEP 393)
API для доступа вне кучи Java, которое появилось в Java 14, остаётся в инкубационном статусе с некоторыми изменениями.
Инфраструктура
Enable C++14 Language Features (JEP 347)
Кодовая база JDK до Java 16 использовала стандарты C++98/03. При этом с Java 11 код стал собираться версией с более новым стандартом, однако в нём всё ещё нельзя было использовать возможности стандарта C++11/14. Теперь же часть из этих возможностей использовать можно: в гиде по стилю HotSpot определён список возможностей C++11/14, которые можно использовать и которые нельзя.
Migrate from Mercurial to Git (JEP 357) и Migrate to GitHub (JEP 369)
Совершён переход репозиториев JDK на Git и GitHub. Миграция была полностью завершена в сентябре 2020 года, и разработка Java 16 уже полностью велась в новом репозитории.
Переход на GitHub облегчил процесс принятия изменений контрибьюторами. Теперь изменения предлагаются через привычные большинству пользователей пулл-реквесты, и большая часть процесса автоматизирована с помощью команд и ботов. Подробнее про процесс можно прочитать на странице проекта Skara.
Также сейчас обсуждается переход на Git более старых версий JDK: jdk11u и, возможно, jdk8u.
Java 16 является STS-релизом, у которого выйдет только два обновления.
Если вы не хотите пропускать новости о Java, то подписывайтесь на Telegram-канал miniJUG
Java 16 — новые синтаксические возможности языка
В марте этого года Oracle выпускает 16-ю версию Java, а уже осенью выйдет 17-я версия — следующая версия с долгосрочной поддержкой (LTS). Вряд ли за пол года появятся какие-то существенные нововведения, а потому уже сейчас можно взглянуть на то, с чем мы будем работать в ближайшие несколько лет. С момента выхода 11-й версии — текущей LTS версии Java, компанией Oracle было внедрено большое количество новых функций — от новых синтаксических конструкций до новых алгоритмов сборки мусора. В данной статье рассмотрим новые синтаксические возможности языка, появившиеся в версиях 12 — 16.
Записи (Records). JEP 395
Традиционные классы в Java довольно перегружены деталями, особенно если речь идет о POJO классах, являющихся простыми неизменяемыми (immutable) агрегатами данных. Такой класс, оформленный по правилам, содержит большое количество не очень ценного и повторяющегося кода, такого как конструкторы, методы чтения полей, методы equals(), hashCode() и toString(). Например, взгляните на класс Point, предназначенный для хранения координат на плоскости:
class Point < private final int x; private final int y; Point(int x, int y) < this.x = x; this.y = y; >int x() < return x; >int y() < return y; >public boolean equals(Object o) < if (!(o instanceof Point)) return false; Point other = (Point) o; return other.x == x && other.y == y; >public int hashCode() < return Objects.hash(x, y); >public String toString() < return String.format("Point[x=%d, y=%d]", x, y); >>
Для того, чтобы создавать такие классы было проще и компактнее, был введен новый тип класса — записи. Объявление такого класса состоит из описания его состояния, а JVM затем сама генерирует API, соответсвующее его объявлению. Это значит, что записи жертвуют некоторой свободой декларирования — возможностью отделить API класса от его внутреннего представления, но являются более компактными.
Объявление записи состоит из имени, опциональных параметров типа, заголовка и тела класса. Заголовок состоит из компонентов класса, которые являются переменными, формирующими его состояние, например:
record Point(int x, int y)
Для записей многие стандартные вещи генерируются автоматически:
- Для каждого компонента из заголовка генерируется финальное приватное поле и метод чтения. Обратите внимание, что методы чтения именуются не стандартным для Java способом. Например, для атрибута x из класса Point метод чтения называется x(), а не getX().
- Публичный конструктор с сигнатурой, совпадающей с заголовком класса, который инициализирует каждое поле значением, переданным при создании объекта (канонический конструктор).
- Методы equals() и hashCode(), которые гарантируют, что 2 записи «равны», если они одного типа и имеют одинаковые значения соответствующих полей.
- Метод toString().
Канонический конструктор можно определить явно, при этом список параметров конструктора должен быть идентичным заголовку записи, например:
record Point(int x, int y) < Point(int x, int y) < if (x < 0 || x >100 || y < 0 || y >100) < throw new IllegalArgumentException("Point coordinates must be between 0 and 100"); >this.x = x; this.y = y; > >
Канонический конструктор может иметь компактную форму — в этом случае у него не должно быть явных параметров. Параметры будут объявлены неявно, а в теле конструктора нельзя присваивать значения полям записи — они будут присвоены автоматически в самом конце. Компактная форма записи конструктора хорошо подходит для проверки или нормализации параметров без необходимости писать лишний код по инициализации полей. Например, эквивалентный предыдущему конструктор будет выглядеть так:
record Point(int x, int y) < Point < if (x < 0 || x >100 || y < 0 || y >100) < throw new IllegalArgumentException("Point coordinates must be between 0 and 100"); >> >
На записи накладываются некоторые ограничения:
- Записи не могут наследоваться от других классов. Родительским классом для записи всегда является java.lang.Record. Это связано с тем, что иначе они имели бы унаследованное состояние, помимо состояния описанного в заголовке.
- Классы записей являются финальными и не могут быть абстрактными.
- Поля записей являются финальными.
- Нельзя добавлять поля и блоки инициализации экземпляра.
- Разрешается переопределять генерируемые методы, но тип возвращаемого значения должен в точности совпадать с типом значения генерируемого метода.
- Нельзя добавлять нативные методы.
В остальном записи являются обычными классами:
- Записи могут быть верхнеуровневыми или вложенными, могут быть параметризованными.
- Записи могут иметь статические методы, поля и инициализаторы, а также методы экземпляра.
- Записи могут реализовывать интерфейсы.
- Записи могут иметь вложенные типы, в том числе и вложенные записи. Вложенные записи являются статическими по умолчанию, иначе они имели бы доступ к состоянию родительского объекта.
- Класс записи и компоненты его заголовка могут быть декорированы аннотациями. Аннотации компонентов затем переносятся на поля, методы и параметры конструктора в зависимости от типа аннотации. Аннотации типов на типах компонентов также переносятся в места использования этих типов.
- Объекты записей можно сериализовать и десериализовать, однако процесс сериaлизации/десериализации нельзя настраивать writeObject(), readObject(), readObjectNoData(), writeExternal(), readExternal().
Статические члены внутренних классов
Как известно внутренние классы в Java не могут иметь статических членов. Это значило бы, что внутренний класс не мог бы иметь записей. Это ограничение было ослаблено, проверил на следующем примере:
public class Outer < class Inner < private String id; private static String idPrefix = "Inner_"; Inner(String id) < this.id = idPrefix + id; >static class StaticClass < >record Point(int x, int y) < >> public static void main(String[] args) < Inner inner = new Outer().new Inner("1"); System.out.println(inner.id); Inner.StaticClass staticClass = new Inner.StaticClass(); System.out.println(staticClass); Inner.Point point = new Inner.Point(1, 2); System.out.println(point); >>
java --enable-preview --source 16 Outer.java Inner_1 jdk16.Outer$Inner$StaticClass@6b67034 Point[x=1, y=2]
Текстовые блоки. JEP 378
Традиционно, задавать в Java многострочный текст было не очень удобно:
String html + " \n" + " Hello, world
\n" + " \n" + "