HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-21
1 <p>Показываем главные фичи новой версии Java на реальных примерах. Это LTS-выпуск с поддержкой до 2029 года.</p>
1 <p>Показываем главные фичи новой версии Java на реальных примерах. Это LTS-выпуск с поддержкой до 2029 года.</p>
2 <p>Преподаватель Skillbox. Пишет про Java, учит Go. Помнит рассвет PHP и как "грабить корованы".</p>
2 <p>Преподаватель Skillbox. Пишет про Java, учит Go. Помнит рассвет PHP и как "грабить корованы".</p>
3 <p>14 сентября 2021 года<a>вышла</a>семнадцатая версия Java. Это LTS-релиз (long-term support), поэтому его будут поддерживать до 2029 года. Предыдущим LTS была Java 11, которая вышла ещё в 2018 году.</p>
3 <p>14 сентября 2021 года<a>вышла</a>семнадцатая версия Java. Это LTS-релиз (long-term support), поэтому его будут поддерживать до 2029 года. Предыдущим LTS была Java 11, которая вышла ещё в 2018 году.</p>
4 <p>В отличие от промежуточных версий, LTS используют в большей части проектов. Разберёмся, какие новые инструменты и конструкции появились в языке за три года.</p>
4 <p>В отличие от промежуточных версий, LTS используют в большей части проектов. Разберёмся, какие новые инструменты и конструкции появились в языке за три года.</p>
5 <p>Чтобы получить значение из switch-выражения, раньше приходилось создавать отдельную переменную и постоянно использовать break;. Вот как это выглядело:</p>
5 <p>Чтобы получить значение из switch-выражения, раньше приходилось создавать отдельную переменную и постоянно использовать break;. Вот как это выглядело:</p>
6 String season; switch (month) { case JANUARY: case FEBRUARY: season = "winter"; break; case MARCH: case APRIL: case MAY: season = "spring"; break; case JUNE: case JULY: case AUGUST: season = "summer"; break; case SEPTEMBER: case OCTOBER: case NOVEMBER: season = "autumn"; break; case DECEMBER: season = "winter"; break; default: throw new IllegalArgumentException(); }<p>В Java 14 появился новый формат записи, который помогает получать результат выбора и записывать выражение компактнее. Если перечислены все возможные варианты, ветка default теперь не нужна:</p>
6 String season; switch (month) { case JANUARY: case FEBRUARY: season = "winter"; break; case MARCH: case APRIL: case MAY: season = "spring"; break; case JUNE: case JULY: case AUGUST: season = "summer"; break; case SEPTEMBER: case OCTOBER: case NOVEMBER: season = "autumn"; break; case DECEMBER: season = "winter"; break; default: throw new IllegalArgumentException(); }<p>В Java 14 появился новый формат записи, который помогает получать результат выбора и записывать выражение компактнее. Если перечислены все возможные варианты, ветка default теперь не нужна:</p>
7 String season = switch (month) { case JANUARY, FEBRUARY -&gt; "winter"; //несколько вариантов case MARCH, APRIL, MAY -&gt; "spring"; case JUNE, JULY, AUGUST -&gt; "summer"; case SEPTEMBER, OCTOBER, NOVEMBER -&gt; "autumn"; case DECEMBER -&gt; "winter"; //oдин вариант }<p>Но веткой default можно задать сообщение об ошибке:</p>
7 String season = switch (month) { case JANUARY, FEBRUARY -&gt; "winter"; //несколько вариантов case MARCH, APRIL, MAY -&gt; "spring"; case JUNE, JULY, AUGUST -&gt; "summer"; case SEPTEMBER, OCTOBER, NOVEMBER -&gt; "autumn"; case DECEMBER -&gt; "winter"; //oдин вариант }<p>Но веткой default можно задать сообщение об ошибке:</p>
8 String season = switch (month) { case "JAN", "FEB" -&gt; "winter"; default -&gt; throw new IllegalArgumentException("no such case:" + month); }<p>Если в значение (case) нужно записать выражение, его заключают в фигурные скобки {} и для возврата значения используют ключевое слово yield:</p>
8 String season = switch (month) { case "JAN", "FEB" -&gt; "winter"; default -&gt; throw new IllegalArgumentException("no such case:" + month); }<p>Если в значение (case) нужно записать выражение, его заключают в фигурные скобки {} и для возврата значения используют ключевое слово yield:</p>
9 String season = switch (month) { case JANUARY, FEBRUARY -&gt; "winter"; case MARCH, APRIL, MAY -&gt; "spring"; case JUNE, JULY, AUGUST -&gt; "summer"; case SEPTEMBER, OCTOBER, NOVEMBER -&gt; { System.out.println("winter is coming!"); yield "autumn"; } case DECEMBER -&gt; "winter"; }<p>Switch-выражениям не обязательно возвращать определённое значение:</p>
9 String season = switch (month) { case JANUARY, FEBRUARY -&gt; "winter"; case MARCH, APRIL, MAY -&gt; "spring"; case JUNE, JULY, AUGUST -&gt; "summer"; case SEPTEMBER, OCTOBER, NOVEMBER -&gt; { System.out.println("winter is coming!"); yield "autumn"; } case DECEMBER -&gt; "winter"; }<p>Switch-выражениям не обязательно возвращать определённое значение:</p>
10 switch (order) { case NATURAL -&gt; Arrays.sort(strings, Comparator.naturalOrder()); case REVERSE -&gt; Arrays.sort(strings, Comparator.reverseOrder()); }<p>Подробнее о switch-выражениях пишут на сайте OpenJDK -<a>JEP 361: Switch Expressions</a>.</p>
10 switch (order) { case NATURAL -&gt; Arrays.sort(strings, Comparator.naturalOrder()); case REVERSE -&gt; Arrays.sort(strings, Comparator.reverseOrder()); }<p>Подробнее о switch-выражениях пишут на сайте OpenJDK -<a>JEP 361: Switch Expressions</a>.</p>
11 <p>Если раньше нужно было использовать литерал в несколько строк, его собирали через конкатенацию:</p>
11 <p>Если раньше нужно было использовать литерал в несколько строк, его собирали через конкатенацию:</p>
12 String query = "SELECT Students.name as \"Name\", SUM(Courses.duration) as \"Duration\"\n" + "FROM Students\n" + "JOIN Subscriptions ON Students.id = Subscriptions.student_id\n" + "JOIN Courses ON Subscriptions.course_id = Courses.id\n" + "GROUP BY Students.id\n" + "ORDER BY Students.name";<p>После того как появились текстовые блоки, это делают одним блоком. Текст с переносами и кавычками заключают в тройные кавычки:</p>
12 String query = "SELECT Students.name as \"Name\", SUM(Courses.duration) as \"Duration\"\n" + "FROM Students\n" + "JOIN Subscriptions ON Students.id = Subscriptions.student_id\n" + "JOIN Courses ON Subscriptions.course_id = Courses.id\n" + "GROUP BY Students.id\n" + "ORDER BY Students.name";<p>После того как появились текстовые блоки, это делают одним блоком. Текст с переносами и кавычками заключают в тройные кавычки:</p>
13 String query = """ SELECT Students.name as "Name", SUM(Courses.duration) as "Duration" FROM Students JOIN Subscriptions ON Students.id = Subscriptions.student_id JOIN Courses ON Subscriptions.course_id = Courses.id GROUP BY Students.id ORDER BY Students.name"""; System.out.println(query);<p>Вот как этот код отобразится при выводе в консоль (примечание: слева не будет пробелов или отступов):</p>
13 String query = """ SELECT Students.name as "Name", SUM(Courses.duration) as "Duration" FROM Students JOIN Subscriptions ON Students.id = Subscriptions.student_id JOIN Courses ON Subscriptions.course_id = Courses.id GROUP BY Students.id ORDER BY Students.name"""; System.out.println(query);<p>Вот как этот код отобразится при выводе в консоль (примечание: слева не будет пробелов или отступов):</p>
14 Скриншот: Мария Помазкина / Skillbox Media<p>Размер отступа зависит от отступа в предыдущей строке:</p>
14 Скриншот: Мария Помазкина / Skillbox Media<p>Размер отступа зависит от отступа в предыдущей строке:</p>
15 String query = """ SELECT Students.name as "Name", SUM(Courses.duration) as "Duration" FROM Students JOIN Subscriptions ON Students.id = Subscriptions.student_id JOIN Courses ON Subscriptions.course_id = Courses.id GROUP BY Students.id ORDER BY Students.name"""; System.out.println(query);Скриншот: Мария Помазкина / Skillbox Media<p>Левая граница строки общая для всего блока, правую определяет последний символ каждой строки:</p>
15 String query = """ SELECT Students.name as "Name", SUM(Courses.duration) as "Duration" FROM Students JOIN Subscriptions ON Students.id = Subscriptions.student_id JOIN Courses ON Subscriptions.course_id = Courses.id GROUP BY Students.id ORDER BY Students.name"""; System.out.println(query);Скриншот: Мария Помазкина / Skillbox Media<p>Левая граница строки общая для всего блока, правую определяет последний символ каждой строки:</p>
16 Скриншот: Мария Помазкина / Skillboxa Media<p>Если знак """ поставить не за последним элементом, а разместить на новой строке, то после каждой строки появится перенос:</p>
16 Скриншот: Мария Помазкина / Skillboxa Media<p>Если знак """ поставить не за последним элементом, а разместить на новой строке, то после каждой строки появится перенос:</p>
17 <p>Java 15 и выше:</p>
17 <p>Java 15 и выше:</p>
18 String query = """ line1 line2 line3 """;<p>Ранние версии Java:</p>
18 String query = """ line1 line2 line3 """;<p>Ранние версии Java:</p>
19 String query = "line1\nline2\nline3\n";<p>Чтобы разместить всё в одну строку, нужно экранировать переносы - добавлять в конце строк символ \:</p>
19 String query = "line1\nline2\nline3\n";<p>Чтобы разместить всё в одну строку, нужно экранировать переносы - добавлять в конце строк символ \:</p>
20 <p>Java 15 и выше:</p>
20 <p>Java 15 и выше:</p>
21 String query = """ line1\ line2\ line3""";<p>Ранние версии Java:</p>
21 String query = """ line1\ line2\ line3""";<p>Ранние версии Java:</p>
22 String query = "line1line2line3";<p>Писать многострочные тексты стало намного проще - код приятно читается.</p>
22 String query = "line1line2line3";<p>Писать многострочные тексты стало намного проще - код приятно читается.</p>
23 <p>Подробнее о текстовых блоках читайте на сайте OpenJDK -<a>JEP 378: Text Blocks</a>.</p>
23 <p>Подробнее о текстовых блоках читайте на сайте OpenJDK -<a>JEP 378: Text Blocks</a>.</p>
24 <p>Чтобы проверить, к какому классу относится объект, используют оператор instanceof. Если нужно проверить объект и привести его к нужному виду, раньше объявляли переменную, присваивали ей тип, а затем проверяли объект:</p>
24 <p>Чтобы проверить, к какому классу относится объект, используют оператор instanceof. Если нужно проверить объект и привести его к нужному виду, раньше объявляли переменную, присваивали ей тип, а затем проверяли объект:</p>
25 Object string = "this is string!"; if(string instanceof String){ String realString = (String) string; System.out.println(realString); }<p>Начиная с Java 16 присвоение не требуется. Значение переменной можно задать прямо в выражении:</p>
25 Object string = "this is string!"; if(string instanceof String){ String realString = (String) string; System.out.println(realString); }<p>Начиная с Java 16 присвоение не требуется. Значение переменной можно задать прямо в выражении:</p>
26 if(string instanceof String realString){ System.out.println(realString); }<p>Если условие проверки не выполняется, оператор instanceof не ограничивается фигурными скобками внутри условия if, а проверяет код дальше:</p>
26 if(string instanceof String realString){ System.out.println(realString); }<p>Если условие проверки не выполняется, оператор instanceof не ограничивается фигурными скобками внутри условия if, а проверяет код дальше:</p>
27 Object object = 23; if (!(object instanceof Number number)) { throw new IllegalArgumentException("this is not a Number!"); } System.out.prin<p>То есть проверка !(object instanceof Number number) выдаёт результат false, и после выхода из if мы можем использовать number для реализации своей логики.</p>
27 Object object = 23; if (!(object instanceof Number number)) { throw new IllegalArgumentException("this is not a Number!"); } System.out.prin<p>То есть проверка !(object instanceof Number number) выдаёт результат false, и после выхода из if мы можем использовать number для реализации своей логики.</p>
28 <p>Подробности:<a>JEP 394: Pattern Matching for instanceof</a>.</p>
28 <p>Подробности:<a>JEP 394: Pattern Matching for instanceof</a>.</p>
29 <p>Класс record - одна из самых упоминаемых фич новой версии Java. Она позволяет быстро писать иммутабельные POJO-классы, с ней не нужно повторять одинаковые методы: геттеры, toString(), equals() и hashCode().</p>
29 <p>Класс record - одна из самых упоминаемых фич новой версии Java. Она позволяет быстро писать иммутабельные POJO-классы, с ней не нужно повторять одинаковые методы: геттеры, toString(), equals() и hashCode().</p>
30 <p>Напишем старым методом класс Student c двумя полями - именем и названием факультета:</p>
30 <p>Напишем старым методом класс Student c двумя полями - именем и названием факультета:</p>
31 public class Student { private final String name; private final CourseType courseType; public Student(String name, CourseType courseType) { this.name = name; this.courseType = courseType; } public String getName() { return name; } public CourseType getCourseType() { return courseType; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Student student = (Student) o; return name.equals(student.name) &amp;&amp; courseType == student.courseType; } @Override public int hashCode() { return Objects.hash(name, courseType); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", courseType=" + courseType + '}'; } }<p>У класса всего два поля, но много бойлерплейтного кода.</p>
31 public class Student { private final String name; private final CourseType courseType; public Student(String name, CourseType courseType) { this.name = name; this.courseType = courseType; } public String getName() { return name; } public CourseType getCourseType() { return courseType; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Student student = (Student) o; return name.equals(student.name) &amp;&amp; courseType == student.courseType; } @Override public int hashCode() { return Objects.hash(name, courseType); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", courseType=" + courseType + '}'; } }<p>У класса всего два поля, но много бойлерплейтного кода.</p>
32 <p>Чтобы проверить, как работает класс, сделаем два объекта, выведем их в консоль и сравним:</p>
32 <p>Чтобы проверить, как работает класс, сделаем два объекта, выведем их в консоль и сравним:</p>
33 var student = new Student("Alex", CourseType.MATH); var studentSame = new Student("Alex", CourseType.MATH); System.out.println(student); System.out.println(studentSame.equals(student)); System.out.println(studentSame.hashCode() == student.hashCode());<p>Так код будет выглядеть в консоли:</p>
33 var student = new Student("Alex", CourseType.MATH); var studentSame = new Student("Alex", CourseType.MATH); System.out.println(student); System.out.println(studentSame.equals(student)); System.out.println(studentSame.hashCode() == student.hashCode());<p>Так код будет выглядеть в консоли:</p>
34 Скриншот: Мария Помазкина / Skillbox Media<p>До того как в Java появился класс record, нам помогал плагин и библиотека<a>Lombok</a> - она позволяет указать аннотациями, какие методы нужно сгенерировать для класса на этапе компиляции. С ней класс может выглядеть так:</p>
34 Скриншот: Мария Помазкина / Skillbox Media<p>До того как в Java появился класс record, нам помогал плагин и библиотека<a>Lombok</a> - она позволяет указать аннотациями, какие методы нужно сгенерировать для класса на этапе компиляции. С ней класс может выглядеть так:</p>
35 @Data @RequiredArgsConstructor public class Student { private final String name; private final CourseType courseType; }<p>@Data генерирует геттеры, сеттеры, equals(), hashCode() и toString(). @RequiredArgsConstructor создаёт конструктор с итоговыми параметрами класса и добавляет аргументы в порядке объявления. Такая запись намного компактнее, но это даётся ценой лишней зависимости и плагина для среды разработки.</p>
35 @Data @RequiredArgsConstructor public class Student { private final String name; private final CourseType courseType; }<p>@Data генерирует геттеры, сеттеры, equals(), hashCode() и toString(). @RequiredArgsConstructor создаёт конструктор с итоговыми параметрами класса и добавляет аргументы в порядке объявления. Такая запись намного компактнее, но это даётся ценой лишней зависимости и плагина для среды разработки.</p>
36 <p>Если запустить тестовый код, видно, что формат преобразования в строку отличается, но в остальном всё работает одинаково:</p>
36 <p>Если запустить тестовый код, видно, что формат преобразования в строку отличается, но в остальном всё работает одинаково:</p>
37 Скриншот: Мария Помазкина / Skillbox Media<p>Класс record избавляет от бойлерплейтного кода. Для этого не нужны внешние плагины, достаточно встроенных возможностей языка. Чтобы создать класс, нужно указать только два поля - конструктор, геттеры, equals(), hashCode() и toString() уже включены в класс.</p>
37 Скриншот: Мария Помазкина / Skillbox Media<p>Класс record избавляет от бойлерплейтного кода. Для этого не нужны внешние плагины, достаточно встроенных возможностей языка. Чтобы создать класс, нужно указать только два поля - конструктор, геттеры, equals(), hashCode() и toString() уже включены в класс.</p>
38 <p>Вот как класс выглядит в новой записи. Сэкономили сорок строк:</p>
38 <p>Вот как класс выглядит в новой записи. Сэкономили сорок строк:</p>
39 public record Student(String name, CourseType courseType) {}<p>Запустим тестовый код и увидим тот же результат:</p>
39 public record Student(String name, CourseType courseType) {}<p>Запустим тестовый код и увидим тот же результат:</p>
40 Скриншот: Мария Помазкина / Skillbox Media<p>У геттеров больше нет приставки get., к методу мы обращаемся по имени переменной:</p>
40 Скриншот: Мария Помазкина / Skillbox Media<p>У геттеров больше нет приставки get., к методу мы обращаемся по имени переменной:</p>
41 student.name(); //Alex student.courseType(); //MATH<p>Если нужна валидация данных, конструктор можно расширить или написать свой:</p>
41 student.name(); //Alex student.courseType(); //MATH<p>Если нужна валидация данных, конструктор можно расширить или написать свой:</p>
42 <p>Расширенный конструктор:</p>
42 <p>Расширенный конструктор:</p>
43 public record Student(String name, CourseType courseType) { public Student { if (name == null || name.isBlank()) { throw new IllegalArgumentException(); } } }<p>Кастомный конструктор:</p>
43 public record Student(String name, CourseType courseType) { public Student { if (name == null || name.isBlank()) { throw new IllegalArgumentException(); } } }<p>Кастомный конструктор:</p>
44 public record Student(String name, CourseType courseType) { public Student(String name){ this(name, CourseType.MATH); } }<p>При этом стандартный конструктор со всеми параметрами класса остаётся доступным.</p>
44 public record Student(String name, CourseType courseType) { public Student(String name){ this(name, CourseType.MATH); } }<p>При этом стандартный конструктор со всеми параметрами класса остаётся доступным.</p>
45 <p>У класса record есть ограничения и особенности:</p>
45 <p>У класса record есть ограничения и особенности:</p>
46 <ul><li>все объявленные поля получают модификатор final;</li>
46 <ul><li>все объявленные поля получают модификатор final;</li>
47 <li>все поля класса объявляются в заголовке, дополнительные объявить нельзя:</li>
47 <li>все поля класса объявляются в заголовке, дополнительные объявить нельзя:</li>
48 </ul>//ошибка компиляции: public record Student(String name, CourseType courseType) { private int id; }<ul><li>можно объявлять static-поля класса;</li>
48 </ul>//ошибка компиляции: public record Student(String name, CourseType courseType) { private int id; }<ul><li>можно объявлять static-поля класса;</li>
49 <li>класс record неявно объявлен как final, поэтому его нельзя наследовать;</li>
49 <li>класс record неявно объявлен как final, поэтому его нельзя наследовать;</li>
50 <li>он не может быть абстрактным и наследовать другие классы;</li>
50 <li>он не может быть абстрактным и наследовать другие классы;</li>
51 <li>можно добавлять свои конструкторы;</li>
51 <li>можно добавлять свои конструкторы;</li>
52 <li>для конструктора можно использовать проверку аргументов;</li>
52 <li>для конструктора можно использовать проверку аргументов;</li>
53 <li>можно переопределить стандартные методы - геттеры, toString(), equals() и hashCode();</li>
53 <li>можно переопределить стандартные методы - геттеры, toString(), equals() и hashCode();</li>
54 <li>можно добавлять статические и нестатические методы.</li>
54 <li>можно добавлять статические и нестатические методы.</li>
55 </ul><p>Благодаря компактному синтаксису класс records несложно обновлять локально:</p>
55 </ul><p>Благодаря компактному синтаксису класс records несложно обновлять локально:</p>
56 public static List&lt;Student&gt; findTreeStudentsByAlphabet(List&lt;Student&gt; students) { record CourseTypeToFirstLetter(Student student, char firstLetter) {} return students.stream() .map(st -&gt; new CourseTypeToFirstLetter(st, st.name().charAt(0))) .sorted((Comparator.comparing(CourseTypeToFirstLetter::firstLetter))) .map(CourseTypeToFirstLetter::student) .limit(3) .collect(Collectors.toList()); }<p>Это упрощает код - структура данных хранится в этом же методе, нет необходимости создавать её в другом месте и слишком расширять область видимости.</p>
56 public static List&lt;Student&gt; findTreeStudentsByAlphabet(List&lt;Student&gt; students) { record CourseTypeToFirstLetter(Student student, char firstLetter) {} return students.stream() .map(st -&gt; new CourseTypeToFirstLetter(st, st.name().charAt(0))) .sorted((Comparator.comparing(CourseTypeToFirstLetter::firstLetter))) .map(CourseTypeToFirstLetter::student) .limit(3) .collect(Collectors.toList()); }<p>Это упрощает код - структура данных хранится в этом же методе, нет необходимости создавать её в другом месте и слишком расширять область видимости.</p>
57 <p>Подробнее о текстовых блоках написано на сайте OpenJDK -<a>JEP 395: Records</a>.</p>
57 <p>Подробнее о текстовых блоках написано на сайте OpenJDK -<a>JEP 395: Records</a>.</p>
58 <p>Sealed class дословно переводится как "запечатанный класс". В этом классе нужно сразу объявить список классов-наследников, потому что кроме них наследников быть не может. Это похоже на enum, только в разрезе наследования.</p>
58 <p>Sealed class дословно переводится как "запечатанный класс". В этом классе нужно сразу объявить список классов-наследников, потому что кроме них наследников быть не может. Это похоже на enum, только в разрезе наследования.</p>
59 <p>Посмотрим, как это выглядит в коде:</p>
59 <p>Посмотрим, как это выглядит в коде:</p>
60 sealed class Person permits Student, Teacher, Curator {}<p>Классы Student, Teacher и Curator должны быть в том же пакете или модуле, что и Person. Кроме этого, у них обязательно должен быть один из модификаторов:</p>
60 sealed class Person permits Student, Teacher, Curator {}<p>Классы Student, Teacher и Curator должны быть в том же пакете или модуле, что и Person. Кроме этого, у них обязательно должен быть один из модификаторов:</p>
61 <ul><li>final, если класс запрещён к дальнейшему наследованию:</li>
61 <ul><li>final, если класс запрещён к дальнейшему наследованию:</li>
62 </ul>public final class Student extends Person{}<ul><li>sealed, если наследование допустимо, но с заранее указанным списком наследников:</li>
62 </ul>public final class Student extends Person{}<ul><li>sealed, если наследование допустимо, но с заранее указанным списком наследников:</li>
63 </ul>public sealed class Teacher extends Person permits MathTeacher, LanguageTeacher {}<ul><li>non-sealed, когда для класса нужно снять любые ограничения по наследованию:</li>
63 </ul>public sealed class Teacher extends Person permits MathTeacher, LanguageTeacher {}<ul><li>non-sealed, когда для класса нужно снять любые ограничения по наследованию:</li>
64 </ul>public non-sealed class Curator extends Person {}<p>У интерфейсов тоже может быть модификатор sealed:</p>
64 </ul>public non-sealed class Curator extends Person {}<p>У интерфейсов тоже может быть модификатор sealed:</p>
65 sealed interface Person permits Student, Teacher, Curator { }<p>С учётом record мы можем имплементировать интерфейс и записать класс Student:</p>
65 sealed interface Person permits Student, Teacher, Curator { }<p>С учётом record мы можем имплементировать интерфейс и записать класс Student:</p>
66 public record Student(String name) implements Person{}<p>Здесь record по умолчанию final, поэтому ограничения класса sealed соблюдены.</p>
66 public record Student(String name) implements Person{}<p>Здесь record по умолчанию final, поэтому ограничения класса sealed соблюдены.</p>
67 <p>Запечатанные классы помогают установить ограничение на число наследников, когда их набор определён и его не собираются часто менять. Это похоже на перечисление (enum), но sealed class гибче, потому что одни ветки наследования можно открыть для расширения, а другие ограничить только для использования.</p>
67 <p>Запечатанные классы помогают установить ограничение на число наследников, когда их набор определён и его не собираются часто менять. Это похоже на перечисление (enum), но sealed class гибче, потому что одни ветки наследования можно открыть для расширения, а другие ограничить только для использования.</p>
68 <p>Подробности:<a>JEP 409: Sealed Classes</a>.</p>
68 <p>Подробности:<a>JEP 409: Sealed Classes</a>.</p>
69 <p>В этой статье мы рассказали только о самых заметных изменениях в синтаксисе. А вообще, Java 17 позволяет писать более понятный и компактный код и принёс немало полезных возможностей и ограничений.</p>
69 <p>В этой статье мы рассказали только о самых заметных изменениях в синтаксисе. А вообще, Java 17 позволяет писать более понятный и компактный код и принёс немало полезных возможностей и ограничений.</p>
70 <p>Много JEP-нововведений (JDK Enhancement Proposal) в виртуальной машине, сборщике мусора, новых методах и уже существующих классах. Полный список изменений в Java 17 можно изучить на <a>сайте Oracle</a>.</p>
70 <p>Много JEP-нововведений (JDK Enhancement Proposal) в виртуальной машине, сборщике мусора, новых методах и уже существующих классах. Полный список изменений в Java 17 можно изучить на <a>сайте Oracle</a>.</p>
71 <a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>
71 <a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>