Статьи

Запись Java

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 -&gt; compilable compilable -&gt; compilable ограничение. Как генератор может использовать эту функцию, которая будет доступна в следующем выпуске 1.3.0 Java :: Geci, будет подробно описано в следующей статье.

навынос

Вывод этой статьи заключается в том, что вы можете использовать записи Java с Java 8, 9, … даже до того, как они станут доступны.

Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Java Record

Мнения, высказанные участниками Java Code Geeks, являются их собственными.