Новые возможности в Java версий 12 — 17

Kate

Administrator
Команда форума

Быстрый тур по новым, готовым к работе функциям при обновлении с Java 11 до Java 17.​

Через три года после Java 11 - на данный момент последней версии с долгосрочной поддержкой (LTS), Java 17 LTS будет выпущена в сентябре 2021 года. Пришло время сделать краткий обзор новых функций, которыми разработчики могут пользоваться после обновления с 11 до 17. Обратите внимание, что было внесено гораздо больше улучшений - в этой статье основное внимание уделяется тем функциям, которые могут напрямую использоваться большинством разработчиков:
  • Switch выражения (JEP 361)
  • Текстовые блоки (JEP 378)
  • Инструмент для упаковки (JEP 392)
  • Сопоставление с образцом для instanceof (JEP 394)
  • Записи (JEP 395)
  • Запечатанные классы (JEP 409)

Switch выражения​

Теперь switch может возвращать значение, как и выражение:
// assign the group of the given planet to a variable
String group = switch (planet) {
case MERCURY, VENUS, EARTH, MARS -> "inner planet";
case JUPITER, SATURN, URANUS, NEPTUNE -> "outer planet";
};
Если правая часть одного case требует большего количества кода, его можно записать внутри блока, а значение возвращается с помощью yield:
// print the group of the given planet, and some more info,
// and assign the group of the given planet to a variable
String group = switch (planet) {
case EARTH, MARS -> {
System.out.println("inner planet");
System.out.println("made up mostly of rock");
yield "inner";
}
case JUPITER, SATURN -> {
System.out.println("outer planet");
System.out.println("ball of gas");
yield "outer";
}
};
Однако switch с использованием новых меток со стрелками не требует возврата значения, как и void выражение:
// print the group of the given planet
// without returning anything
switch (planet) {
case EARTH, MARS -> System.out.println("inner planet");
case JUPITER, SATURN -> System.out.println("outer planet");
}
По сравнению с традиционным переключателем, новое Switch выражение
  • Использует «->» вместо «:»
  • Позволяет использовать несколько констант для каждого case
  • Не имеет сквозной семантики (т. е. не требует break)
  • Делает переменные, определенные внутри ветви case, локальными для этой ветви
Более того, компилятор гарантирует полноту переключения в том смысле, что выполняется ровно один из случаев, что означает, что либо
  • Все возможные значения перечислены как case (как в приведенном выше перечислении, состоящем из восьми планет), или
  • Должна быть предоставлена ветка «default».

Текстовые блоки​

Текстовые блоки позволяют писать многострочные строки, содержащие двойные кавычки, без использования \n или \" escape-последовательностей:
String block = """
Multi-line text
with indentation
and "double quotes"!
""";
Текстовый блок открывается тремя двойными кавычками, """за которыми следует разрыв строки, и закрывается тремя двойными кавычками.
Компилятор Java использует интеллектуальный алгоритм для удаления начального пробела из результирующей строки, чтобы:
  • отступ, необходимый только для лучшей читаемости исходного кода Java, был удален.
  • отступ, относящийся к самой строке, остлтся нетронутым
В приведенном выше примере результирующая строка выглядит следующим образом, каждая из . обозначает пробел:
Multi-line.text
.with.indentation
..and."double.quotes"!
Представьте себе вертикальную полосу, охватывающую по высоте весь текстовый блок, перемещающуюся слева направо и удаляющую пробелы, пока она не коснется первого не пробельного символа. Ограничитель закрывающего текстового блока также считается, поэтому переместите его на две позиции влево.
String block = """
Multi-line text
with indentation
and "double quotes"!
""";
Результат представлен в следующей строке:
..Multi-line.text
...with.indentation
....and."double.quotes"!
Кроме того, из каждой строки удаляется конечный пробел, чего можно избежать, используя новую escape-последовательность \s.
Разрывы строк внутри текстовых блоков можно экранировать:
String block = """
No \
line \
breaks \
at \
all \
please\
""";
Результатом является следующая строка без разрывов строк:
No.line.breaks.at.all.please
В качестве альтернативы последний разрыв строки также можно удалить, добавив закрывающий разделитель непосредственно в конец строки:
String block = """
No final line break
at the end of this string, please""";
Вставка переменных в текстовый блок может выполняться как обычно с помощью статического метода String::format или с помощью нового метода экземпляра String::formatted, который немного короче для записи:
String block = """
%s marks the spot.
""".formatted("X");

Инструмент для упаковки​

Предположим, у вас есть JAR-файл demo.jar в каталоге lib вместе с дополнительными JAR-файлами зависимостей. Следующая команда:
jpackage --name demo --input lib --main-jar demo.jar --main-class demo.Main
упаковывает это демонстрационное приложение в собственный формат, соответствующий вашей текущей платформе:
  • Linux: deb или rpm
  • Windows: msi или exe
  • macOS: pkg или dmg
Результирующий пакет также содержит те части JDK, которые требуются для запуска приложения, а также собственный модуль запуска. Это означает, что пользователи могут устанавливать, запускать и удалять приложение стандартным способом, зависящим от платформы, без предварительной установки Java.
Кросс-компиляция не поддерживается: если вам нужен пакет для пользователей Windows, вы должны создать его с помощью jpackage на машине Windows.
Создание пакета можно настроить с помощью многих других параметров, которые задокументированы на странице руководства jpackage.

Сопоставление с образцом для Instanceof​

Сопоставление с образцом (Pattern matching) для instanceof позволяет исключить шаблонный код для выполнения приведений после сравнения типов:
Object o = "string disguised as object";
if (o instanceof String s) {
System.out.println(s.toUpperCase());
}
В приведенном выше примере область действия новой переменной s интуитивно ограничена if веткой. Чтобы быть точным, переменная находится в области видимости, в которой гарантировано совпадение шаблона, что также делает следующий код допустимым:
if (o instanceof String s && !s.isEmpty()) {
System.out.println(s.toUpperCase());
}
А также наоборот:
if (!(o instanceof String s)) {
throw new RuntimeException("expecting string");
}
// s is in scope here!
System.out.println(s.toUpperCase());

Записи​

Записи (Records) сокращают шаблонный код для классов, которые являются простыми носителями данных:
record Point(int x, int y) { }
Эта строка кода в результате приводит к созданию класса записи, в котором автоматически определены:
  • поля для x и y (как private и final)
  • канонический конструктор для всех полей
  • геттеры для всех полей
  • equals, hashCode и toString (с учетом всех полей)
// canonical constructor
Point p = new Point(1, 2);

// getters - without "get" prefix
p.x();
p.y();

// equals / hashCode / toString
p.equals(new Point(1, 2)); // true
p.hashCode(); // depends on values of x and y
p.toString(); // Point[x=1, y=2]
Некоторые из наиболее важных ограничений классов записей заключаются в том, что они:
  • неизменяемы (поскольку их поля являются private и final)
  • неявно final
  • невозможно определить дополнительные поля экземпляра
  • всегда наследует от Record класса
Однако можно:
  • определить дополнительные методы
  • реализовать интерфейсы
  • кастомизировать канонический конструктор и аксессоры
record Point(int x, int y) {

// explicit canonical constructor
Point {

// custom validations
if (x < 0 || y < 0)
throw new IllegalArgumentException("no negative points allowed");

// custom adjustments (usually counter-intuitive)
x += 1000;
y += 1000;

// assignment to fields happens automatically at the end

}

// explicit accessor
public int x() {
// custom code here...
return this.x;
}
}
Кроме того, внутри метода можно определить локальную запись:
public void withLocalRecord() {
record Point(int x, int y) { };
Point p = new Point(1, 2);
}

Sealed классы​

Sealed (запечатанный) класс явно перечисляет допустимые прямые подклассы. Другие классы не могут наследовать от этого класса:
public sealed class Parent
permits ChildA, ChildB, ChildC { ... }
Точно так же запечатанный интерфейс явно перечисляет разрешенные прямые субинтерфейсы и реализующие классы:
sealed interface Parent
permits ChildA, ChildB, ChildC { ... }
Классы или интерфейсы в permits списке должны находиться в одном пакете (или в том же модуле, если родитель находится в названном модуле).
permits список может быть опущена, если подклассы (или интерфейсы) расположены в том же файле:
public sealed class Parent {
final class Child1 extends Parent {}
final class Child2 extends Parent {}
final class Child3 extends Parent {}
}
Каждый подкласс или интерфейс в permits списке должен использовать только один из следующих модификаторов:
  • final (запрещает дальнейшее наследование; только для подклассов, поскольку интерфейсы не могут быть final)
  • sealed (допускает дальнейшее, ограниченное наследование)
  • non-sealed (снова разрешает неограниченное наследование)

 
Сверху