В прошлый раз мы тестировали улучшенный оператор
У записей есть свой JEP, однако он не сильно блещет подробностями, поэтому многое придётся пробовать и проверять самим. Да, можно конечно, открыть спецификацию Java SE, но, мне кажется, гораздо интереснее самим начать писать код и смотреть на поведение компилятора в тех или иных ситуациях. Так что заваривайте чаёк и располагайтесь поудобнее. Поехали.
В отличие от прошлого раза, когда мне пришлось собирать специальную ветку JDK для тестирования
Для начала реализуем классический пример с
Ага, компилятор создал следующее:
С конструктором и методами чтения всё понятно, но интересно, как именно реализованы
В реализации
Но что-то мы слишком сильно углубились. Вернёмся к нашимбаранам записям. Давайте попробуем создать экземпляр
Таким образом,
ОК. А теперь давайте проверим компилятор на стойкость. Сделаем что-нибудь некорректное, например, добавим поле:
Значит, можно добавлять только статические поля.
Интересно, что будет, если сделать компоненты
Пожалуй, это логичный запрет. Чтобы не было иллюзии того, будто бы компоненты станут изменяемыми, если убрать final. Да и аналогичное правило есть у
Что если переопределить тип метода доступа?
Это абсолютно логично.
А если изменить видимость?
Тоже нельзя.
Наследоваться от классов запрещено, даже от
А вот реализовывать интерфейсы можно:
Интересно немного поиграться с каноническим конструктором. Во-первых, давайте напишем компактный канонический конструктор, т.е. канонический конструктор без аргументов, и добавим туда валидацию входных параметров:
Заработало. А вот интересно, заработает ли, если написать тот же самый код, но через
Интересная деталь. Вряд ли мне это сильно помешает в жизни, так как я не любитель писать
Давайте попробуем явный канонический конструктор. Интересно, можно ли переименовать параметры?
Оказывается, нельзя переименовать. Но я не вижу ничего плохого в таком ограничении. Код чище будет.
А что там с порядком инициализации?
Сначала напечатался
Хорошо. Как насчёт добавления неканонического конструктора? Например, конструктора без аргументов:
Ага, забыли написать
Что насчёт дженериков?
Ничего сверхъестественного. Ну разве что надо помнить, что параметры типа нужно ставить раньше параметров записи.
Можно ли создать запись без компонент?
Почему нет.
Какие вещи мы ещё не попробовали? Что там со вложенными записями?
Значит, вложенные записи всегдя являются статическими (как и
Хм, сработало. Думаю, это баг. Или просто недоделка: такое поведение унаследовалось от обычных локальных классов, которые умеют захватывать внешние effectively final переменные, а для записей поправить забыли.
Один больной вопрос, который меня интересует: можно ли создать несколько публичных записей в одном файле?
Нельзя. Интересно, будет ли это проблемой в реальных проектах? Наверняка многие захотят писать очень много записей, чтобы моделировать свои сущности. Тогда придётся всех их раскладывать по собственным файлам, либо использовать вложенные записи.
Напоследок я ещё хотел бы поиграться с рефлексией. Как во время выполнения узнать информацию о компонентах, которые содержит запись? Для это можно использовать метод Class.getRecordComponents():
Также я заметил, что в Java 14 появился новый тип аннотации специально для компонентов записей: ElementType.RECORD_COMPONENT. А что будет, если использовать старые типы
Ага, код компилируется, значит работают все три. Ну это логично. Интересно, а будут ли они «протаскиваться» на поля?
Значит, «протаскиваются» только аннотации
На этом, пожалуй, я закончу, потому что статья и так уже вышла довольно громоздкой. Можно было бы «копать» ещё долго и глубоко, тестируя всякие разные краевые случаи, но думаю, текущего уровня глубины более чем достаточно.
Записи — это несомненно крутая и очень ожидаемая сообществом вещь, которая в будущем будет экономить нам время и избавит нас от огромного количества шаблонного кода. Сейчас записи уже практически готовы, и осталось только подождать, когда починят некоторые шероховатости и выпустят общедоступный релиз Java 14. Правда, потом ещё нужно будет подождать 1-2 релиза, когда записи станут стабильными, но при большом желании их можно использовать в preview-режиме.
А те, кто не спешат переходить с Java 8, думаю, надо дождаться сентября 2021 года, и сразу перейти на Java 17, где уже будут стабильные выражения switch, блоки текста, улучшенный instanceof, записи и запечатанные типы (с большой вероятностью).
P.S. Если вы не хотите пропускать мои новости и статьи о Java, то рекомендую вам подписаться на мой канал в Telegram.
Всех с наступающим!
instanceof
, который появится в грядущей, 14-й версии Java (выйдет в марте 2020). Сегодня я хотел бы исследовать в деталях вторую синтаксическую возможность, которая также появится в Java 14: записи (records).У записей есть свой JEP, однако он не сильно блещет подробностями, поэтому многое придётся пробовать и проверять самим. Да, можно конечно, открыть спецификацию Java SE, но, мне кажется, гораздо интереснее самим начать писать код и смотреть на поведение компилятора в тех или иных ситуациях. Так что заваривайте чаёк и располагайтесь поудобнее. Поехали.
В отличие от прошлого раза, когда мне пришлось собирать специальную ветку JDK для тестирования
instanceof
, сейчас всё это уже присутствует в главной ветке и доступно в ранней сборке JDK 14, которую я и скачал.Для начала реализуем классический пример с
Point
и скомпилируем его:record Point(float x, float y) {
}
> javac --enable-preview --release 14 Point.java
Note: Point.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
javac
успешно скомпилировал файл Point.class
. Давайте его дизассемблируем и посмотрим, что нам там нагенерировал компилятор:> javap -private Point.class
Compiled from "Point.java"
final class Point extends java.lang.Record {
private final float x;
private final float y;
public Point(float, float);
public java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public float x();
public float y();
}
Ага, компилятор создал следующее:
- Финальный класс, отнаследованный от java.lang.Record (по аналогии с
enum
, которые наследуются отjava.lang.Enum
). - Приватные финальные поля
x
иy
. - Публичный конструктор, совпадающий с сигнатурой самой записи. Такой конструктор называется каноническим.
- Реализации
toString()
,hashCode()
иequals()
. Интересно, чтоhashCode()
иequals()
являютсяfinal
, аtoString()
— нет. Это вряд ли на что-то может повлиять, так как сам классfinal
, но кто-нибудь знает, зачем так сделали? (Я нет) - Методы чтения полей.
С конструктором и методами чтения всё понятно, но интересно, как именно реализованы
toString()
, hashCode()
и equals()
? Давайте посмотрим. Для этого запустим javap
с флагом -verbose
:Длинный вывод дизассемблера
> javap -private -verbose Point.class
Classfile Point.class
Last modified 29 дек. 2019 г.; size 1157 bytes
SHA-256 checksum 24fe5489a6a01a7232f45bd7739a961c30d7f6e24400a3e3df2ec026cc94c0eb
Compiled from "Point.java"
final class Point extends java.lang.Record
minor version: 65535
major version: 58
flags: (0x0030) ACC_FINAL, ACC_SUPER
this_class: #8 // Point
super_class: #2 // java/lang/Record
interfaces: 0, fields: 2, methods: 6, attributes: 4
Constant pool:
#1 = Methodref #2.#3 // java/lang/Record."<init>":()V
#2 = Class #4 // java/lang/Record
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Record
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // Point.x:F
#8 = Class #10 // Point
#9 = NameAndType #11:#12 // x:F
#10 = Utf8 Point
#11 = Utf8 x
#12 = Utf8 F
#13 = Fieldref #8.#14 // Point.y:F
#14 = NameAndType #15:#12 // y:F
#15 = Utf8 y
#16 = Fieldref #8.#9 // Point.x:F
#17 = Fieldref #8.#14 // Point.y:F
#18 = InvokeDynamic #0:#19 // #0:toString:(LPoint;)Ljava/lang/String;
#19 = NameAndType #20:#21 // toString:(LPoint;)Ljava/lang/String;
#20 = Utf8 toString
#21 = Utf8 (LPoint;)Ljava/lang/String;
#22 = InvokeDynamic #0:#23 // #0:hashCode:(LPoint;)I
#23 = NameAndType #24:#25 // hashCode:(LPoint;)I
#24 = Utf8 hashCode
#25 = Utf8 (LPoint;)I
#26 = InvokeDynamic #0:#27 // #0:equals:(LPoint;Ljava/lang/Object;)Z
#27 = NameAndType #28:#29 // equals:(LPoint;Ljava/lang/Object;)Z
#28 = Utf8 equals
#29 = Utf8 (LPoint;Ljava/lang/Object;)Z
#30 = Utf8 (FF)V
#31 = Utf8 Code
#32 = Utf8 LineNumberTable
#33 = Utf8 MethodParameters
#34 = Utf8 ()Ljava/lang/String;
#35 = Utf8 ()I
#36 = Utf8 (Ljava/lang/Object;)Z
#37 = Utf8 ()F
#38 = Utf8 SourceFile
#39 = Utf8 Point.java
#40 = Utf8 Record
#41 = Utf8 BootstrapMethods
#42 = MethodHandle 6:#43 // REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
#43 = Methodref #44.#45 // java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
#44 = Class #46 // java/lang/runtime/ObjectMethods
#45 = NameAndType #47:#48 // bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
#46 = Utf8 java/lang/runtime/ObjectMethods
#47 = Utf8 bootstrap
#48 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
#49 = String #50 // x;y
#50 = Utf8 x;y
#51 = MethodHandle 1:#7 // REF_getField Point.x:F
#52 = MethodHandle 1:#13 // REF_getField Point.y:F
#53 = Utf8 InnerClasses
#54 = Class #55 // java/lang/invoke/MethodHandles$Lookup
#55 = Utf8 java/lang/invoke/MethodHandles$Lookup
#56 = Class #57 // java/lang/invoke/MethodHandles
#57 = Utf8 java/lang/invoke/MethodHandles
#58 = Utf8 Lookup
{
private final float x;
descriptor: F
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
private final float y;
descriptor: F
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
public Point(float, float);
descriptor: (FF)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #1 // Method java/lang/Record."<init>":()V
4: aload_0
5: fload_1
6: putfield #7 // Field x:F
9: aload_0
10: fload_2
11: putfield #13 // Field y:F
14: return
LineNumberTable:
line 1: 0
MethodParameters:
Name Flags
x
y
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #18, 0 // InvokeDynamic #0:toString:(LPoint;)Ljava/lang/String;
6: areturn
LineNumberTable:
line 1: 0
public final int hashCode();
descriptor: ()I
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #22, 0 // InvokeDynamic #0:hashCode:(LPoint;)I
6: ireturn
LineNumberTable:
line 1: 0
public final boolean equals(java.lang.Object);
descriptor: (Ljava/lang/Object;)Z
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokedynamic #26, 0 // InvokeDynamic #0:equals:(LPoint;Ljava/lang/Object;)Z
7: ireturn
LineNumberTable:
line 1: 0
public float x();
descriptor: ()F
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #16 // Field x:F
4: freturn
LineNumberTable:
line 1: 0
public float y();
descriptor: ()F
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #17 // Field y:F
4: freturn
LineNumberTable:
line 1: 0
}
SourceFile: "Point.java"
Record:
float x;
descriptor: F
float y;
descriptor: F
BootstrapMethods:
0: #42 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
Method arguments:
#8 Point
#49 x;y
#51 REF_getField Point.x:F
#52 REF_getField Point.y:F
InnerClasses:
public static final #58= #54 of #56; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
В реализации
toString()
, hashCode()
и equals()
мы видим invokedynamic
. Значит, логика этих методов будет генерироваться лениво самой виртуальной машиной. Я не большой специалист по рантайму, но думаю, что это сделано для лучшей эффективности. Например, если в будущем придумают какой-нибудь более быстрый хеш, то в таком подходе старый скомпилированный код получит все преимущества новой версии. Также это уменьшает размер class-файлов.Но что-то мы слишком сильно углубились. Вернёмся к нашим
Point
и посмотрим, как работают методы. С этого момента я больше не буду использовать javac
и просто буду запускать java-файл напрямую:…
public class Main {
public static void main(String[] args) {
var point = new Point(1, 2);
System.out.println(point);
System.out.println("hashCode = " + point.hashCode());
System.out.println("hashCode2 = " + Objects.hash(point.x(), point.y()));
var point2 = new Point(1, 2);
System.out.println(point.equals(point2));
}
}
record Point(float x, float y) {
}
> java --enable-preview --source 14 Main.java
Note: Main.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
Point[x=1.0, y=2.0]
hashCode = -260046848
hashCode2 = -260045887
true
Таким образом,
toString()
и equals()
работают как я и ожидал (ну разве что toString()
использует квадратные скобки, а я хотел бы фигурные). А вот hashCode()
работает иначе. Я почему-то полагал, что он будет совместимым с Objects.hash()
. Но ничто нам не мешает создать свою реализацию hashCode()
. Давайте так и сделаем, а заодно перенесём метод main()
внутрь:…
public record Point(float x, float y) {
@Override
public int hashCode() {
return Objects.hash(x, y);
}
public static void main(String[] args) {
System.out.println(new Point(1, 2).hashCode());
}
}
> java --enable-preview --source 14 Point.java
Note: Point.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
-260045887
ОК. А теперь давайте проверим компилятор на стойкость. Сделаем что-нибудь некорректное, например, добавим поле:
public record Point(float x, float y) {
private float z;
}
Point.java:2: error: field declaration must be static
private float z;
^
(consider replacing field with record component)
Значит, можно добавлять только статические поля.
Интересно, что будет, если сделать компоненты
final
? Станут ещё финальнее?public record Point(final float x, final float y) {
}
Point.java:1: error: record components cannot have modifiers
public record Point(final float x, final float y) {
^
Point.java:1: error: record components cannot have modifiers
public record Point(final float x, final float y) {
^
Пожалуй, это логичный запрет. Чтобы не было иллюзии того, будто бы компоненты станут изменяемыми, если убрать final. Да и аналогичное правило есть у
enum
, так что ничего нового:enum A {
final X; // No modifiers allowed for enum constants
}
Что если переопределить тип метода доступа?
public record Point(float x, float y) {
public double x() {
return x;
}
}
Point.java:2: error: invalid accessor method in record Point
public double x() {
^
(return type of accessor method x() is not compatible with type of record component x)
Это абсолютно логично.
А если изменить видимость?
public record Point(float x, float y) {
private float x() {
return x;
}
}
Point.java:2: error: invalid accessor method in record Point
private float x() {
^
(accessor method must be public)
Тоже нельзя.
Наследоваться от классов запрещено, даже от
Object
:public record Point(float x, float y) extends Object {
}
Point.java:1: error: '{' expected
public record Point(float x, float y) extends Object {
^
А вот реализовывать интерфейсы можно:
public record Point(float x, float y) implements PointLike {
public static void main(String[] args) {
PointLike point = new Point(1, 2);
System.out.println(point.x());
System.out.println(point.y());
}
}
public interface PointLike {
float x();
float y();
}
> java --enable-preview --source 14 Point.java
Note: Point.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
1.0
2.0
Интересно немного поиграться с каноническим конструктором. Во-первых, давайте напишем компактный канонический конструктор, т.е. канонический конструктор без аргументов, и добавим туда валидацию входных параметров:
public record Point(float x, float y) {
public Point {
if (Float.isNaN(x) || Float.isNaN(y)) {
throw new IllegalArgumentException("NaN");
}
}
public static void main(String[] args) {
System.out.println(new Point(Float.NaN, 2));
}
}
…
Exception in thread "main" java.lang.IllegalArgumentException: NaN
at Point.<init>(Point.java:4)
at Point.main(Point.java:9)
Заработало. А вот интересно, заработает ли, если написать тот же самый код, но через
return
:public record Point(float x, float y) {
public Point {
if (!Float.isNaN(x) && !Float.isNaN(y)) {
return;
}
throw new IllegalArgumentException("NaN");
}
}
Point.java:2: error: invalid compact constructor in record Point(float,float)
public Point {
^
(compact constructor must not have return statements)
Интересная деталь. Вряд ли мне это сильно помешает в жизни, так как я не любитель писать
return
, но всяким разработчикам IDE это нужно иметь в виду.Давайте попробуем явный канонический конструктор. Интересно, можно ли переименовать параметры?
public record Point(float x, float y) {
public Point(float _x, float _y) {
if (Float.isNaN(_x) || Float.isNaN(_y)) {
throw new IllegalArgumentException("NaN");
}
this.x = _x;
this.y = _y;
}
}
Point.java:2: error: invalid canonical constructor in record Point
public Point(float _x, float _y) {
^
(invalid parameter names in canonical constructor)
Оказывается, нельзя переименовать. Но я не вижу ничего плохого в таком ограничении. Код чище будет.
А что там с порядком инициализации?
public record Point(float x, float y) {
public Point {
System.out.println(this);
}
public static void main(String[] args) {
System.out.println(new Point(-1, 2));
}
}
…
Point[x=0.0, y=0.0]
Point[x=-1.0, y=2.0]
Сначала напечатался
Point
с нулями, значит присваивание полей произошло в самом конце конструктора, после System.out.println(this)
.Хорошо. Как насчёт добавления неканонического конструктора? Например, конструктора без аргументов:
public record Point(float x, float y) {
public Point() {
}
}
Point.java:2: error: constructor is not canonical, so its first statement must invoke another constructor
public Point() {
^
Ага, забыли написать
this(0, 0)
. Но не будем исправлять и проверять это.Что насчёт дженериков?
public record Point<A extends Number>(A x, A y) {
public static void main(String[] args) {
System.out.println(new Point<>(-1, 2));
}
}
> java --enable-preview --source 14 Point.java
Note: Point.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
Point[x=-1, y=2]
Ничего сверхъестественного. Ну разве что надо помнить, что параметры типа нужно ставить раньше параметров записи.
Можно ли создать запись без компонент?
public record None() {
public static void main(String[] args) {
System.out.println(new None());
}
}
> java --enable-preview --source 14 None.java
Note: None.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
None[]
Почему нет.
Какие вещи мы ещё не попробовали? Что там со вложенными записями?
record Point(int x, int y) {
record Nested(int z) {
void print() {
System.out.println(x);
}
}
}
Point.java:4: error: non-static record component x cannot be referenced from a static context
System.out.println(x);
^
Значит, вложенные записи всегдя являются статическими (как и
enum
). Если это так, то что если объявить локальную запись? По идее, тогда она не должна захватывать внешний нестатический контекст:public class Main {
public static void main(String[] args) {
record Point(int x, int y) {
void print() {
System.out.println(Arrays.toString(args));
}
}
new Point(1, 2).print();
}
}
> java --enable-preview --source 14 Main.java
Note: Main.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
[]
Хм, сработало. Думаю, это баг. Или просто недоделка: такое поведение унаследовалось от обычных локальных классов, которые умеют захватывать внешние effectively final переменные, а для записей поправить забыли.
Один больной вопрос, который меня интересует: можно ли создать несколько публичных записей в одном файле?
public record Point(float x, float y) {
}
public record Point2(float x, float y) {
}
> javac --enable-preview --release 14 Point.java
Point.java:4: error: class Point2 is public, should be declared in a file named Point2.java
public record Point2(float x, float y) {
^
Нельзя. Интересно, будет ли это проблемой в реальных проектах? Наверняка многие захотят писать очень много записей, чтобы моделировать свои сущности. Тогда придётся всех их раскладывать по собственным файлам, либо использовать вложенные записи.
Напоследок я ещё хотел бы поиграться с рефлексией. Как во время выполнения узнать информацию о компонентах, которые содержит запись? Для это можно использовать метод Class.getRecordComponents():
import java.lang.reflect.RecordComponent;
public record Point(float x, float y) {
public static void main(String[] args) {
var point = new Point(1, 2);
for (RecordComponent component : point.getClass().getRecordComponents()) {
System.out.println(component);
}
}
}
> java --enable-preview --source 14 Point.java
Note: Point.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
float x
float y
Также я заметил, что в Java 14 появился новый тип аннотации специально для компонентов записей: ElementType.RECORD_COMPONENT. А что будет, если использовать старые типы
FIELD
и PARAMETER
? Ведь компоненты вроде бы как и не поля, и не параметры:public record Point(
@FieldAnnotation @ComponentAnnotation float x,
@ParamAnnotation @ComponentAnnotation float y) {
}
@Target(ElementType.FIELD)
@interface FieldAnnotation { }
@Target(ElementType.PARAMETER)
@interface ParamAnnotation { }
@Target(ElementType.RECORD_COMPONENT)
@interface ComponentAnnotation { }
Ага, код компилируется, значит работают все три. Ну это логично. Интересно, а будут ли они «протаскиваться» на поля?
public record Point(
@FieldAnnotation @ComponentAnnotation float x,
@ParamAnnotation @ComponentAnnotation float y) {
public static void main(String[] args) {
var point = new Point(1, 2);
Field[] fields = point.getClass().getDeclaredFields();
for (Field field : fields) {
for (Annotation annotation : field.getAnnotations()) {
System.out.println(field + ": " + annotation);
}
}
}
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldAnnotation { }
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface ParamAnnotation { }
@Target(ElementType.RECORD_COMPONENT)
@Retention(RetentionPolicy.RUNTIME)
@interface ComponentAnnotation { }
> java --enable-preview --source 14 Point.java
Note: Point.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
private final float Point.x: @FieldAnnotation()
Значит, «протаскиваются» только аннотации
FIELD
, но не RECORD_COMPONENT
и PARAMETER
.На этом, пожалуй, я закончу, потому что статья и так уже вышла довольно громоздкой. Можно было бы «копать» ещё долго и глубоко, тестируя всякие разные краевые случаи, но думаю, текущего уровня глубины более чем достаточно.
Выводы
Записи — это несомненно крутая и очень ожидаемая сообществом вещь, которая в будущем будет экономить нам время и избавит нас от огромного количества шаблонного кода. Сейчас записи уже практически готовы, и осталось только подождать, когда починят некоторые шероховатости и выпустят общедоступный релиз Java 14. Правда, потом ещё нужно будет подождать 1-2 релиза, когда записи станут стабильными, но при большом желании их можно использовать в preview-режиме.
А те, кто не спешат переходить с Java 8, думаю, надо дождаться сентября 2021 года, и сразу перейти на Java 17, где уже будут стабильные выражения switch, блоки текста, улучшенный instanceof, записи и запечатанные типы (с большой вероятностью).
P.S. Если вы не хотите пропускать мои новости и статьи о Java, то рекомендую вам подписаться на мой канал в Telegram.
Всех с наступающим!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какие фичи Java вам поскорее хочется начать использовать? (можно выбирать несколько вариантов)
54.55% switch expressions48
43.18% text blocks38
45.45% pattern matching for instanceof40
54.55% records48
20.45% sealed types18
12.5% мне всё это не нужно11
Проголосовали 88 пользователей. Воздержались 27 пользователей.