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 -> "winter"; //несколько вариантов case MARCH, APRIL, MAY -> "spring"; case JUNE, JULY, AUGUST -> "summer"; case SEPTEMBER, OCTOBER, NOVEMBER -> "autumn"; case DECEMBER -> "winter"; //oдин вариант }<p>Но веткой default можно задать сообщение об ошибке:</p>
7
String season = switch (month) { case JANUARY, FEBRUARY -> "winter"; //несколько вариантов case MARCH, APRIL, MAY -> "spring"; case JUNE, JULY, AUGUST -> "summer"; case SEPTEMBER, OCTOBER, NOVEMBER -> "autumn"; case DECEMBER -> "winter"; //oдин вариант }<p>Но веткой default можно задать сообщение об ошибке:</p>
8
String season = switch (month) { case "JAN", "FEB" -> "winter"; default -> throw new IllegalArgumentException("no such case:" + month); }<p>Если в значение (case) нужно записать выражение, его заключают в фигурные скобки {} и для возврата значения используют ключевое слово yield:</p>
8
String season = switch (month) { case "JAN", "FEB" -> "winter"; default -> throw new IllegalArgumentException("no such case:" + month); }<p>Если в значение (case) нужно записать выражение, его заключают в фигурные скобки {} и для возврата значения используют ключевое слово yield:</p>
9
String season = switch (month) { case JANUARY, FEBRUARY -> "winter"; case MARCH, APRIL, MAY -> "spring"; case JUNE, JULY, AUGUST -> "summer"; case SEPTEMBER, OCTOBER, NOVEMBER -> { System.out.println("winter is coming!"); yield "autumn"; } case DECEMBER -> "winter"; }<p>Switch-выражениям не обязательно возвращать определённое значение:</p>
9
String season = switch (month) { case JANUARY, FEBRUARY -> "winter"; case MARCH, APRIL, MAY -> "spring"; case JUNE, JULY, AUGUST -> "summer"; case SEPTEMBER, OCTOBER, NOVEMBER -> { System.out.println("winter is coming!"); yield "autumn"; } case DECEMBER -> "winter"; }<p>Switch-выражениям не обязательно возвращать определённое значение:</p>
10
switch (order) { case NATURAL -> Arrays.sort(strings, Comparator.naturalOrder()); case REVERSE -> Arrays.sort(strings, Comparator.reverseOrder()); }<p>Подробнее о switch-выражениях пишут на сайте OpenJDK -<a>JEP 361: Switch Expressions</a>.</p>
10
switch (order) { case NATURAL -> Arrays.sort(strings, Comparator.naturalOrder()); case REVERSE -> 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) && 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) && 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<Student> findTreeStudentsByAlphabet(List<Student> students) { record CourseTypeToFirstLetter(Student student, char firstLetter) {} return students.stream() .map(st -> 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<Student> findTreeStudentsByAlphabet(List<Student> students) { record CourseTypeToFirstLetter(Student student, char firstLetter) {} return students.stream() .map(st -> 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>