Switch-выражения, класс record и запечатанные классы на практике: что нового в Java 17
Показываем главные фичи новой версии Java на реальных примерах. Это LTS-выпуск с поддержкой до 2029 года.
tesla / youtube
14 сентября 2021 года вышла семнадцатая версия Java. Это LTS-релиз (long-term support), поэтому его будут поддерживать до 2029 года. Предыдущим LTS была Java 11, которая вышла ещё в 2018 году.
В отличие от промежуточных версий, LTS используют в большей части проектов. Разберёмся, какие новые инструменты и конструкции появились в языке за три года.
Switch-выражения из Java 14
Чтобы получить значение из switch-выражения, раньше приходилось создавать отдельную переменную и постоянно использовать break;. Вот как это выглядело:
В Java 14 появился новый формат записи, который помогает получать результат выбора и записывать выражение компактнее. Если перечислены все возможные варианты, ветка default теперь не нужна:
Но веткой default можно задать сообщение об ошибке:
Если в значение (case) нужно записать выражение, его заключают в фигурные скобки {} и для возврата значения используют ключевое слово yield:
Switch-выражениям не обязательно возвращать определённое значение:
Подробнее о switch-выражениях пишут на сайте OpenJDK — JEP 361: Switch Expressions.
Текстовые блоки из Java 15
Если раньше нужно было использовать литерал в несколько строк, его собирали через конкатенацию:
После того как появились текстовые блоки, это делают одним блоком. Текст с переносами и кавычками заключают в тройные кавычки:
Вот как этот код отобразится при выводе в консоль (примечание: слева не будет пробелов или отступов):
Размер отступа зависит от отступа в предыдущей строке:
Левая граница строки общая для всего блока, правую определяет последний символ каждой строки:
Если знак """ поставить не за последним элементом, а разместить на новой строке, то после каждой строки появится перенос:
Java 15 и выше:
Ранние версии Java:
Чтобы разместить всё в одну строку, нужно экранировать переносы — добавлять в конце строк символ \:
Java 15 и выше:
Ранние версии Java:
Писать многострочные тексты стало намного проще — код приятно читается.
Подробнее о текстовых блоках читайте на сайте OpenJDK — JEP 378: Text Blocks.
Новые варианты применения instanceof из Java 16
Чтобы проверить, к какому классу относится объект, используют оператор instanceof. Если нужно проверить объект и привести его к нужному виду, раньше объявляли переменную, присваивали ей тип, а затем проверяли объект:
Начиная с Java 16 присвоение не требуется. Значение переменной можно задать прямо в выражении:
Если условие проверки не выполняется, оператор instanceof не ограничивается фигурными скобками внутри условия if, а проверяет код дальше:
То есть проверка !(object instanceof Number number) выдаёт результат false, и после выхода из if мы можем использовать number для реализации своей логики.
Подробности: JEP 394: Pattern Matching for instanceof.
Класс record из Java 16
Класс record — одна из самых упоминаемых фич новой версии Java. Она позволяет быстро писать иммутабельные POJO-классы, с ней не нужно повторять одинаковые методы: геттеры, toString(), equals() и hashCode().
Напишем старым методом класс Student c двумя полями — именем и названием факультета:
У класса всего два поля, но много бойлерплейтного кода.
Чтобы проверить, как работает класс, сделаем два объекта, выведем их в консоль и сравним:
Так код будет выглядеть в консоли:
До того как в Java появился класс record, нам помогал плагин и библиотека Lombok — она позволяет указать аннотациями, какие методы нужно сгенерировать для класса на этапе компиляции. С ней класс может выглядеть так:
@Data генерирует геттеры, сеттеры, equals(), hashCode() и toString(). @RequiredArgsConstructor создаёт конструктор с итоговыми параметрами класса и добавляет аргументы в порядке объявления. Такая запись намного компактнее, но это даётся ценой лишней зависимости и плагина для среды разработки.
Если запустить тестовый код, видно, что формат преобразования в строку отличается, но в остальном всё работает одинаково:
Класс record избавляет от бойлерплейтного кода. Для этого не нужны внешние плагины, достаточно встроенных возможностей языка. Чтобы создать класс, нужно указать только два поля — конструктор, геттеры, equals(), hashCode() и toString() уже включены в класс.
Вот как класс выглядит в новой записи. Сэкономили сорок строк:
Запустим тестовый код и увидим тот же результат:
У геттеров больше нет приставки get., к методу мы обращаемся по имени переменной:
Если нужна валидация данных, конструктор можно расширить или написать свой:
Расширенный конструктор:
Кастомный конструктор:
При этом стандартный конструктор со всеми параметрами класса остаётся доступным.
У класса record есть ограничения и особенности:
- все объявленные поля получают модификатор final;
- все поля класса объявляются в заголовке, дополнительные объявить нельзя:
- можно объявлять static-поля класса;
- класс record неявно объявлен как final, поэтому его нельзя наследовать;
- он не может быть абстрактным и наследовать другие классы;
- можно добавлять свои конструкторы;
- для конструктора можно использовать проверку аргументов;
- можно переопределить стандартные методы — геттеры, toString(), equals() и hashCode();
- можно добавлять статические и нестатические методы.
Благодаря компактному синтаксису класс records несложно обновлять локально:
Это упрощает код — структура данных хранится в этом же методе, нет необходимости создавать её в другом месте и слишком расширять область видимости.
Подробнее о текстовых блоках написано на сайте OpenJDK — JEP 395: Records.
Запечатанные классы из Java 17
Sealed class дословно переводится как «запечатанный класс». В этом классе нужно сразу объявить список классов-наследников, потому что кроме них наследников быть не может. Это похоже на enum, только в разрезе наследования.
Посмотрим, как это выглядит в коде:
Классы Student, Teacher и Curator должны быть в том же пакете или модуле, что и Person. Кроме этого, у них обязательно должен быть один из модификаторов:
- final, если класс запрещён к дальнейшему наследованию:
- sealed, если наследование допустимо, но с заранее указанным списком наследников:
- non-sealed, когда для класса нужно снять любые ограничения по наследованию:
У интерфейсов тоже может быть модификатор sealed:
С учётом record мы можем имплементировать интерфейс и записать класс Student:
Здесь record по умолчанию final, поэтому ограничения класса sealed соблюдены.
Запечатанные классы помогают установить ограничение на число наследников, когда их набор определён и его не собираются часто менять. Это похоже на перечисление (enum), но sealed class гибче, потому что одни ветки наследования можно открыть для расширения, а другие ограничить только для использования.
Подробности: JEP 409: Sealed Classes.
Вывод: что за зверь такой Java 17
В этой статье мы рассказали только о самых заметных изменениях в синтаксисе. А вообще, Java 17 позволяет писать более понятный и компактный код и принёс немало полезных возможностей и ограничений.
Много JEP-нововведений (JDK Enhancement Proposal) в виртуальной машине, сборщике мусора, новых методах и уже существующих классах. Полный список изменений в Java 17 можно изучить на сайте Oracle.