Https://openjdk.java.net/jeps/359 описывает новую функцию Java, которая может / будет реализована в некоторых будущих версиях Java. JEP предлагает иметь новый тип «класса»: запись. Пример в JEP гласит:
|
1
2
3
4
5
6
|
record Range(int lo, int hi) { public Range { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); }} |
По сути, запись будет классом, который намерен иметь только final поля, заданные в конструкторе. На сегодняшний день JEP также позволяет использовать любые другие члены, которые есть у класса, но, по сути, запись — это запись, чистые данные и, возможно, никакой функциональности по своей сути. Описание записи является коротким и конкретным, и в нем исключено множество шаблонов, которые нам понадобятся для кодирования такого класса в Java 13 или менее, или какую бы версию записи не было реализовано. Приведенный выше код с использованием обычной Java будет выглядеть следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
public class Range { final int lo; final int hi; public Range(int lo, int hi) { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); this.lo = lo; this.hi = hi; }} |
Рассматривая мой проект генерации кода Java :: Geci, это было то, что требовало от генератора кода преодоления разрыва между сегодняшним днем и днем, когда новая функция будет доступна на всех производственных платформах.
Таким образом, я начал думать о том, как разработать этот генератор, и столкнулся с несколькими проблемами. Инфраструктура Java :: Geci может конвертировать только компилируемый проект в другой компилируемый проект. Он не может работать как некоторые другие генераторы кода, которые преобразуют неполный исходный код, который не может быть скомпилирован без изменений генератора кода, в полную версию. Это потому, что Java :: Geci работает на этапе тестирования. Чтобы перейти к этапу тестирования, сначала необходимо скомпилировать код. Это известный компромисс и было дизайнерским решением. В большинстве случаев, когда Java :: Geci полезен, с этим легко справиться. С другой стороны, мы получаем преимущество, заключающееся в том, что генераторы не нуждаются в управлении конфигурацией, такой как чтение и интерпретация свойств или файлов XML. Они предоставляют только API, а код, вызывающий их из теста, настраивает генераторы через него. Большим преимуществом является то, что вы можете даже предоставлять обратные вызовы в виде ссылок на методы, лямбда-выражения или экземпляров объектов, которые вызываются генераторами, так что эти генераторы могут иметь полностью открытую структуру в некоторых аспектах их работы.
Почему это важно в этом случае? Генерация записей довольно проста и не требует какой-либо сложной конфигурации, фактически, она вообще не нуждается ни в какой конфигурации. С другой стороны, compilable -> compilable compilable -> compilable ограничения влияют на это. Если вы начнете создавать запись, используя, скажем, Java 8 и Java :: Geci, то ваш ручной код будет выглядеть примерно так:
|
1
2
3
4
5
6
|
@Geci("record")public class Range { final int lo; final int hi;} |
Это не компилируется, потому что ко времени первой компиляции перед началом генерации кода конструктор по умолчанию не инициализирует поля. Поэтому поля не могут быть final :
|
1
2
3
4
5
6
|
@Geci("record")public class Range { int lo; int hi;} |
Запустив генератор мы получим
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package javax0.geci.tests.record;import javax0.geci.annotations.Geci;@Geci("record")public final class Range { final int lo; final int hi; //<editor-fold id="record"> public Range(final int lo, final int hi) { this.lo = lo; this.hi = hi; } public int getLo() { return lo; } public int getHi() { return hi; } @Override public int hashCode() { return java.util.Objects.hash(lo, hi); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Range that = (Range) o; return java.util.Objects.equals(that.lo, lo) && java.util.Objects.equals(that.hi, hi); } //</editor-fold>} |
что этот генератор на самом деле делает то, что
- он генерирует конструктор
- преобразует класс и поля в
finalкак это требуется JEP - генерирует геттеры для полей
- генерирует методы
equals()иhashCode()для класса
Если у класса есть метод void который имеет то же (хотя и без учета регистра) имя, что и класс, например:
|
1
2
3
4
|
public void Range(double hi, long lo) { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); } |
тогда генератор будет
- вызвать этот метод из сгенерированного конструктора,
- изменить список аргументов метода, чтобы он соответствовал текущему списку полей.
|
01
02
03
04
05
06
07
08
09
10
11
|
public void Range(final int lo, final int hi) { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); } //<editor-fold id="record"> public Range(final int lo, final int hi) { Range(lo, hi); this.lo = lo; this.hi = hi; } |
Обратите внимание, что этот подход генерации пытается вести себя как можно ближе к фактической record как предложено в JEP, и генерирует код, который можно преобразовать в новый синтаксис, как только он станет доступен. По этой причине метод валидатора должен иметь то же имя, что и класс. При преобразовании в реальную запись все, что нужно сделать, это удалить ключевое слово void преобразующее метод в конструктор, удалить список аргументов, поскольку он будет неявным, как определено в JEP, и удалить весь сгенерированный код между сгибами редактора. (также автоматически генерируется при первом запуске генератора).
Модификация введенного вручную кода — это новая функция Java :: Geci, которая была вызвана необходимостью генератора записей и была разработана для устранения недостатков compilable -> compilable compilable -> compilable ограничение. Как генератор может использовать эту функцию, которая будет доступна в следующем выпуске 1.3.0 Java :: Geci, будет подробно описано в следующей статье.
навынос
Вывод этой статьи заключается в том, что вы можете использовать записи Java с Java 8, 9, … даже до того, как они станут доступны.
|
Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Java Record Мнения, высказанные участниками Java Code Geeks, являются их собственными. |