[Программирование, Java] Java 16 — новые синтаксические возможности языка
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В марте этого года Oracle выпускает 16-ю версию Java, а уже осенью выйдет 17-я версия - следующая версия с долгосрочной поддержкой (LTS). Вряд ли за пол года появятся какие-то существенные нововведения, а потому уже сейчас можно взглянуть на то, с чем мы будем работать в ближайшие несколько лет. С момента выхода 11-й версии - текущей LTS версии Java, компанией Oracle было внедрено большое количество новых функций - от новых синтаксических конструкций до новых алгоритмов сборки мусора. В данной статье рассмотрим новые синтаксические возможности языка, появившиеся в версиях 12 - 16.Записи (Records). JEP 395.Традиционные классы в Java довольно перегружены деталями, особенно если речь идет о POJO классах, являющихся простыми неизменяемыми (immutable) агрегатами данных. Такой класс, оформленный по правилам, содержит большое количество не очень ценного и повторяющегося кода, такого как конструкторы, методы чтения полей, методы equals(), hashCode() и toString(). Например, взгляните на класс Point, предназначенный для хранения координат на плоскости:
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y = y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
Для того, чтобы создавать такие классы было проще и компактнее, был введен новый тип класса - записи. Объявление такого класса состоит из описания его состояния, а JVM затем сама генерирует API, соответсвующее его объявлению. Это значит, что записи жертвуют некоторой свободой декларирования - возможностью отделить API класса от его внутреннего представления, но являются более компактными.Объявление записи состоит из имени, опциональных параметров типа, заголовка и тела класса. Заголовок состоит из компонентов класса, которые являются переменными, формирующими его состояние, например:
record Point(int x, int y) { }
Для записей многие стандартные вещи генерируются автоматически:
- Для каждого компонента из заголовка генерируется финальное приватное поле и метод чтения. Обратите внимание, что методы чтения именуются не стандартным для Java способом. Например, для атрибута x из класса Point метод чтения называется x(), а не getX().
- Публичный конструктор с сигнатурой, совпадающей с заголовком класса, который инициализирует каждое поле значением, переданным при создании объекта (канонический конструктор).
- Методы equals() и hashCode(), которые гарантируют, что 2 записи "равны", если они одного типа и имеют одинаковые значения соответствующих полей.
- Метод toString().
Канонический конструктор можно определить явно, при этом список параметров конструктора должен быть идентичным заголовку записи, например:
record Point(int x, int y) {
Point(int x, int y) {
if (x < 0 || x > 100 || y < 0 || y > 100) {
throw new IllegalArgumentException("Point coordinates must be between 0 and 100");
}
this.x = x;
this.y = y;
}
}
Канонический конструктор может иметь компактную форму - в этом случае у него не должно быть явных параметров. Параметры будут объявлены неявно, а в теле конструктора нельзя присваивать значения полям записи - они будут присвоены автоматически в самом конце. Компактная форма записи конструктора хорошо подходит для проверки или нормализации параметров без необходимости писать лишний код по инициализации полей. Например, эквивалентный предыдущему конструктор будет выглядеть так:
record Point(int x, int y) {
Point {
if (x < 0 || x > 100 || y < 0 || y > 100) {
throw new IllegalArgumentException("Point coordinates must be between 0 and 100");
}
}
}
На записи накладываются некоторые ограничения:
- Записи не могут наследоваться от других классов. Родительским классом для записи всегда является java.lang.Record. Это связано с тем, что иначе они имели бы унаследованное состояние, помимо состояния описанного в заголовке.
- Классы записей являются финальными и не могут быть абстрактными.
- Поля записей являются финальными.
- Нельзя добавлять поля и блоки инициализации экземпляра.
- Разрешается переопределять генерируемые методы, но тип возвращаемого значения должен в точности совпадать с типом значения генерируемого метода.
- Нельзя добавлять нативные методы.
В остальном записи являются обычными классами:
- Записи могут быть верхнеуровневыми или вложенными, могут быть параметризованными.
- Записи могут иметь статические методы, поля и инициализаторы, а также методы экземпляра.
- Записи могут реализовывать интерфейсы.
- Записи могут иметь вложенные типы, в том числе и вложенные записи. Вложенные записи являются статическими по умолчанию, иначе они имели бы доступ к состоянию родительского объекта.
- Класс записи и компоненты его заголовка могут быть декорированы аннотациями. Аннотации компонентов затем переносятся на поля, методы и параметры конструктора в зависимости от типа аннотации. Аннотации типов на типах компонентов также переносятся в места использования этих типов.
- Объекты записей можно сериализовать и десериализовать, однако процесс стерилизации/десериализации нельзя настраивать writeObject(), readObject(), readObjectNoData(), writeExternal(), readExternal().
Статические члены внутренних классовКак известно внутренние классы в Java не могут иметь статических членов. Это значило бы, что внутренний класс не мог бы иметь записей. Это ограничение было ослаблено, проверил на следующем примере:
public class Outer {
class Inner {
private String id;
private static String idPrefix = "Inner_";
Inner(String id) {
this.id = idPrefix + id;
}
static class StaticClass {
}
record Point(int x, int y) {
}
}
public static void main(String[] args) {
Inner inner = new Outer().new Inner("1");
System.out.println(inner.id);
Inner.StaticClass staticClass = new Inner.StaticClass();
System.out.println(staticClass);
Inner.Point point = new Inner.Point(1, 2);
System.out.println(point);
}
}
java --enable-preview --source 16 Outer.java
Inner_1
jdk16.Outer$Inner$StaticClass@6b67034
Point[x=1, y=2]
Текстовые блоки. JEP 378.Традиционно, задавать в Java многострочный текст было не очень удобно:
String html = "<html>\n" +
" <body>\n" +
" <p>Hello, world</p>\n" +
" </body>\n" +
"</html>\n";
Теперь это можно сделать так:
String html = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
Намного лаконичнее. Есть возможность разбивать длинные строки на несколько строк для удобства восприятия. Для этого используется escape-последовательность \<line-terminator>, например, такую строку:
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore " +
"et dolore magna aliqua.";
можно представить в виде:
String text = """
Lorem ipsum dolor sit amet, consectetur adipiscing \
elit, sed do eiusmod tempor incididunt ut labore \
et dolore magna aliqua.\
""";
Также появилась новая escape-последовательность \s, которая транслируется в единичный пробел (\u0020). Поскольку escape-последовательности транслируются после удаления пробелов в начале и конце строки, её можно использовать как барьер, чтобы помешать удалению пробелов. Например, в примере ниже последовательность \s используется, чтобы сделать каждую строку длиной ровно 6 символов:
String colors = """
red \s
green\s
blue \s
""";
Паттерны для instanceof (Pattern Matching for instanceof). JEP 394.Практически в каждой программе встречается код вида:
if (obj instanceof String) {
String s = (String) obj;
...
}
Проблема этого кода в том, что он излишне многословен. Понятно, что после проверки типа, мы захотим привести объект к нему. Почему бы не сделать это автоматически? Для упрощения этой процедуры и были введены паттерны в оператор instanceof:
if (obj instanceof String s) {
...
}
Область видимости переменной s может быть как внутри блока if (как в примере выше), так и за его пределами, например:
if (!(obj instanceof String s)) {
throw new Exception();
}
System.out.println(s);
Переменную паттерна можно использовать и в выражении оператора if:
if (obj instanceof String s && s.length() > 5) {
System.out.println(s);
}
Однако такой пример приведет к ошибке компиляции:
if (obj instanceof String s || s.length() > 5) { // Error!
...
}
Переменные из паттерна могут затенять поля класса, следует быть внимательным при именовании и использовании переменных:
class Example1 {
String s;
void test1(Object o) {
if (o instanceof String s) {
System.out.println(s); // Field s is shadowed
s = s + "\n"; // Assignment to pattern variable
...
}
System.out.println(s); // Refers to field s
...
}
}
class Example2 {
Point p;
void test2(Object o) {
if (o instanceof Point p) {
// p refers to the pattern variable
...
} else {
// p refers to the field
...
}
}
}
Изолированные типы (Sealed Classes). JEP 397.Изолированные классы и интерфейсы могут быть расширены и реализованы только теми классами и интерфейсами, которым это разрешено. Это позволяет передать компилятору знания о том, что существует ограниченная иерархия каких-либо классов. Для объявления изолированных типов используется модификатор sealed. Затем, после ключевых слов extends и implements идет ключевое слово permits, после которого перечисляются классы, которым разрешено расширять или реализовывать данный класс/интерфейс. Взглянем на пример:
package com.example.geometry;
public abstract sealed class Shape
permits Circle, Rectangle, Square { ... }
... class Circle extends Shape { ... }
... class Rectangle extends Shape { ... }
... class Square extends Shape { ... }
Классы, перечисленные после ключевого слова permits должны находиться рядом с родительским классом: в том же модуле или пакете. Если они малы и их не так много, их можно разместить в одном файле с родительским классом, в этом случае ключевое слово permits можно опустить. Каждый дочерний класс должен быть прямым наследником изолированного класса. Каждый дочерний класс должен использовать один из трех модификаторов:
- Модификатор final, если иерархия типов не должна расширяться далее.
- Модификатор sealed, если иерархия типов может расширяться далее, но в ограниченном ключе.
- Модификатор non-sealed, если эта часть иерархии может расширяться произвольным образом.
Поскольку компилятор теперь обладает знанием того, что иерархия классов ограничена, это должно позволять нам перебирать типы объекта изолированного класса следующим образом:
Shape rotate(Shape shape, double angle) {
if (shape instanceof Circle) return shape;
else if (shape instanceof Rectangle) return shape.rotate(angle);
else if (shape instanceof Square) return shape.rotate(angle);
// no else needed!
}
Однако, мне так и не удалось заставить такой код работать (возможно, потому что это все еще превью реализация):
public class Main {
static abstract sealed class Shape permits Rect, Circle {
}
static final class Rect extends Shape {
}
static final class Circle extends Shape {
}
public Shape getShape(Shape shape) {
if (shape instanceof Rect) return shape;
else if (shape instanceof Circle) return shape;
}
public static void main(String[] args) {
new Main().getShape(new Rect());
}
}
javac -Xlint:preview --enable-preview --release 16 Main.java
Main.java:9: warning: [preview] sealed classes are a preview feature and may be removed in a future release.
static abstract sealed class Shape permits Rect, Circle {
^
Main.java:9: warning: [preview] sealed classes are a preview feature and may be removed in a future release.
static abstract sealed class Shape permits Rect, Circle {
^
Main.java:9: warning: [preview] sealed classes are a preview feature and may be removed in a future release.
static abstract sealed class Shape permits Rect, Circle {
^
Main.java:21: error: missing return statement
}
^
1 error
3 warnings
Switch выражения (Switch Expressions). JEP 361.Использование оператора switch чревато ошибками из-за его сквозной семантики. Взгляните на пример:
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
}
Из-за большого количества ключевых слов break легко запутаться и пропустить его где-то.Кроме того, очень часто оператор switch используется для эмуляции switch выражения, но это не удобно и тоже чревато ошибками:
int numLetters;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
default:
throw new IllegalStateException("Wat: " + day);
}
Для решения перечисленных проблем был введен новый способ записи условий в операторе switch в виде "case L ->" и сам оператор стал еще и выражением.Если условие записано в виде "case L ->", то при его срабатывании выполняется только инструкция справа от него. Сквозная семантика в этом случае не работает. Пример такой записи:
static void howMany(int k) {
switch (k) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
default -> System.out.println("many");
}
}
Теперь рассмотрим пример switch выражения:
static void howMany(int k) {
System.out.println(
switch (k) {
case 1 -> "one";
case 2 -> "two";
default -> "many";
}
);
}
Большинство выражений будут иметь единственную инструкцию справа от условия "case L ->". На случай, если понадобится целый блок, вводится ключевое слово yield для возврата значения из выражения:
int j = switch (day) {
case MONDAY -> 0;
case TUESDAY -> 1;
default -> {
int k = day.toString().length();
int result = f(k);
yield result;
}
};
Условия в switch выражении должны быть исчерпывающими, то есть охватывать все возможные варианты. На практике это означает, что обязательно присутствие общего условия - default (в случае с простым оператором switch это не обязательно). Однако, в случае со switch выражениями на enum типах, которые покрывают все возможные константы, наличие общего условия необязательно. В таком случае, при добавлении новой константы в enum, компилятор выдаст ошибку, чего не случилось бы, будь общее условие задано.ЗаключениеВ данной статье мы рассмотрели новые синтаксические возможности Java 16: записи, текстовые блоки, паттерны для instanceof, изолированные типы и switch выражения. Стоит отметить, что изолированные типы все еще находятся на стадии preview, а потому в Java 17 могут и не войти.Ссылки
- JEP 395. Records
- JEP 378. Text Blocks
- JEP 394. Pattern Matching for instanceof
- JEP 397. Sealed Classes (Second Preview)
- JEP 361. Switch Expressions
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Objective C, История IT, Биографии гиков] Ушел из жизни один из создателей Objective C Брэд Кокс
- [JavaScript, Функциональное программирование] Lens JS как менеджер состояния приложения
- [Программирование, Assembler, Компиляторы] Экстракоды при синтезе программ
- [JavaScript, Разработка игр, Логические игры] DagazServer: Встречайте Garbo Chess
- [Программирование, Symfony] Новое в Symfony 5.2: атрибуты PHP 8 (перевод)
- [Open source, PHP, Программирование] PHP 8 продолжает развитие опенсорсного языка программирования (перевод)
- [JavaScript, Node.JS, Meteor.JS] Мажорный MeteorJS 2.0: HMR, Cloud и другое
- [Open source, Java, Виртуализация, Openshift] Представляем Quarkus на Red Hat OpenShift
- [Ajax, PHP, JavaScript, Программирование] Ajax, REST API OpenCart
- [Программирование, Алгоритмы] Трюк с XOR для собеседований и не только (перевод)
Теги для поиска: #_programmirovanie (Программирование), #_java, #_java, #_oracle, #_java_16, #_jdk_16, #_programmirovanie (
Программирование
), #_java
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 22:03
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В марте этого года Oracle выпускает 16-ю версию Java, а уже осенью выйдет 17-я версия - следующая версия с долгосрочной поддержкой (LTS). Вряд ли за пол года появятся какие-то существенные нововведения, а потому уже сейчас можно взглянуть на то, с чем мы будем работать в ближайшие несколько лет. С момента выхода 11-й версии - текущей LTS версии Java, компанией Oracle было внедрено большое количество новых функций - от новых синтаксических конструкций до новых алгоритмов сборки мусора. В данной статье рассмотрим новые синтаксические возможности языка, появившиеся в версиях 12 - 16.Записи (Records). JEP 395.Традиционные классы в Java довольно перегружены деталями, особенно если речь идет о POJO классах, являющихся простыми неизменяемыми (immutable) агрегатами данных. Такой класс, оформленный по правилам, содержит большое количество не очень ценного и повторяющегося кода, такого как конструкторы, методы чтения полей, методы equals(), hashCode() и toString(). Например, взгляните на класс Point, предназначенный для хранения координат на плоскости: class Point {
private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } int x() { return x; } int y() { return y; } public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return other.x == x && other.y = y; } public int hashCode() { return Objects.hash(x, y); } public String toString() { return String.format("Point[x=%d, y=%d]", x, y); } } record Point(int x, int y) { }
record Point(int x, int y) {
Point(int x, int y) { if (x < 0 || x > 100 || y < 0 || y > 100) { throw new IllegalArgumentException("Point coordinates must be between 0 and 100"); } this.x = x; this.y = y; } } record Point(int x, int y) {
Point { if (x < 0 || x > 100 || y < 0 || y > 100) { throw new IllegalArgumentException("Point coordinates must be between 0 and 100"); } } }
public class Outer {
class Inner { private String id; private static String idPrefix = "Inner_"; Inner(String id) { this.id = idPrefix + id; } static class StaticClass { } record Point(int x, int y) { } } public static void main(String[] args) { Inner inner = new Outer().new Inner("1"); System.out.println(inner.id); Inner.StaticClass staticClass = new Inner.StaticClass(); System.out.println(staticClass); Inner.Point point = new Inner.Point(1, 2); System.out.println(point); } } java --enable-preview --source 16 Outer.java
Inner_1 jdk16.Outer$Inner$StaticClass@6b67034 Point[x=1, y=2] String html = "<html>\n" +
" <body>\n" + " <p>Hello, world</p>\n" + " </body>\n" + "</html>\n"; String html = """
<html> <body> <p>Hello, world</p> </body> </html> """; String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore " + "et dolore magna aliqua."; String text = """
Lorem ipsum dolor sit amet, consectetur adipiscing \ elit, sed do eiusmod tempor incididunt ut labore \ et dolore magna aliqua.\ """; String colors = """
red \s green\s blue \s """; if (obj instanceof String) {
String s = (String) obj; ... } if (obj instanceof String s) {
... } if (!(obj instanceof String s)) {
throw new Exception(); } System.out.println(s); if (obj instanceof String s && s.length() > 5) {
System.out.println(s); } if (obj instanceof String s || s.length() > 5) { // Error!
... } class Example1 {
String s; void test1(Object o) { if (o instanceof String s) { System.out.println(s); // Field s is shadowed s = s + "\n"; // Assignment to pattern variable ... } System.out.println(s); // Refers to field s ... } } class Example2 { Point p; void test2(Object o) { if (o instanceof Point p) { // p refers to the pattern variable ... } else { // p refers to the field ... } } } package com.example.geometry;
public abstract sealed class Shape permits Circle, Rectangle, Square { ... } ... class Circle extends Shape { ... } ... class Rectangle extends Shape { ... } ... class Square extends Shape { ... }
Shape rotate(Shape shape, double angle) {
if (shape instanceof Circle) return shape; else if (shape instanceof Rectangle) return shape.rotate(angle); else if (shape instanceof Square) return shape.rotate(angle); // no else needed! } public class Main {
static abstract sealed class Shape permits Rect, Circle { } static final class Rect extends Shape { } static final class Circle extends Shape { } public Shape getShape(Shape shape) { if (shape instanceof Rect) return shape; else if (shape instanceof Circle) return shape; } public static void main(String[] args) { new Main().getShape(new Rect()); } } javac -Xlint:preview --enable-preview --release 16 Main.java
Main.java:9: warning: [preview] sealed classes are a preview feature and may be removed in a future release. static abstract sealed class Shape permits Rect, Circle { ^ Main.java:9: warning: [preview] sealed classes are a preview feature and may be removed in a future release. static abstract sealed class Shape permits Rect, Circle { ^ Main.java:9: warning: [preview] sealed classes are a preview feature and may be removed in a future release. static abstract sealed class Shape permits Rect, Circle { ^ Main.java:21: error: missing return statement } ^ 1 error 3 warnings switch (day) {
case MONDAY: case FRIDAY: case SUNDAY: System.out.println(6); break; case TUESDAY: System.out.println(7); break; case THURSDAY: case SATURDAY: System.out.println(8); break; case WEDNESDAY: System.out.println(9); break; } int numLetters;
switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException("Wat: " + day); } static void howMany(int k) {
switch (k) { case 1 -> System.out.println("one"); case 2 -> System.out.println("two"); default -> System.out.println("many"); } } static void howMany(int k) {
System.out.println( switch (k) { case 1 -> "one"; case 2 -> "two"; default -> "many"; } ); } int j = switch (day) {
case MONDAY -> 0; case TUESDAY -> 1; default -> { int k = day.toString().length(); int result = f(k); yield result; } };
=========== Источник: habr.com =========== Похожие новости:
Программирование ), #_java |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 22:03
Часовой пояс: UTC + 5