Как стать автором
Обновить

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

Время на прочтение6 мин
Количество просмотров12K
Автор оригинала: Nenad Jovanovic

Быстрый тур по новым, готовым к работе функциям при обновлении с 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)

  • канонический конструктор для всех полей

  • геттеры для всех полей

  • equalshashCode и 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 (снова разрешает неограниченное наследование)

Теги:
Хабы:
Всего голосов 11: ↑11 и ↓0+11
Комментарии8

Публикации

Истории

Работа

Java разработчик
355 вакансий

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань