Как стать автором
Обновить

Java Records (JEP 359)

Время на прочтение7 мин
Количество просмотров45K
Автор оригинала: Vojtech Ruzicka

Проблема


Одной из проблем Java является ее многословность и объем необходимого стандартного кода. Это общеизвестно.


Давайте рассмотрим простой класс Cat на Java. Мы хотим, чтобы каждый объект Cat имел следующие атрибуты (поля):


  • Имя
  • Количество жизней
  • Цвет

Довольно просто, верно? Теперь давайте посмотрим на код в Java. Для простоты, давайте сделаем наш класс immutable (неизменным) — без сеттеров, мы все настроим в нашем конструкторе.


public final class Cat {

    private final String name;
    private final int numberOfLives;
    private final String color;

    public Cat(String name, int numberOfLives, String color) {
        this.name = name;
        this.numberOfLives = numberOfLives;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public int getNumberOfLives() {
        return numberOfLives;
    }

    public String getColor() {
        return color;
    }
}

Это уже довольно длинно, не так ли?


Но станет хуже. Мы также хотим иметь некоторую базовую реализацию equals() и hashCode().


@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Cat cat = (Cat) o;
    return numberOfLives == cat.numberOfLives &&
            Objects.equals(name, cat.name) &&
            Objects.equals(color, cat.color);
}

@Override
public int hashCode() {
    return Objects.hash(name, numberOfLives, color);
}

Мы уже закончили? Не совсем, нам еще понадобится хороший метод toString():


@Override
public String toString() {
    return "Cat{" +
            "name='" + name + '\'' +
            ", numberOfLives=" + numberOfLives +
            ", color='" + color + '\'' +
            '}';
}

И наконец мы все сделали. Это около пятидесяти строк кода! Это довольно мучительно писать (хотя ваша IDE может помочь здесь) и трудно читать. Что еще хуже, трудно найти дополнительную функциональность (например, новые методы) в этом шаблонном коде.


В этих пятидесяти строках есть только три строки, которые действительно интересны и несут некоторую информацию:


private final String name;
private final int numberOfLives;
private final String color;

Остальное — простой шаблонный код, который предсказуем и может быть автоматически сгенерирован на основе этих трех строк. Ваша IDE может сделать это, и есть инструменты, такие как Lombok, которые могут сделать это и для вас.


В Java вы часто используете классы, которые просто хранят данные, как наш Cat. Реализация всегда почти одинакова — набор полей, геттеров, equals(), hashCode() и toString(). Часто бывает полезно иметь их неизменными, если это возможно, что имеет много преимуществ. Но писать и читать такие классы — это много работы, так как в них много кода. И это подвержено ошибкам. Кто знает, правильный ли ваш код для hashCode() и equals()?


Records


Java 14 пытается решить эту проблему, вводя новый тип под названием Record, он описан в JEP 359: Records (Preview).


Тот же класс длиной 50 строк из приведенного выше примера можно записать в виде записи, подобной этой:


public record Cat(String name, int numberOfLives, String color) { }

Код намного меньше, верно?


Функциональность такая же, как в нашем предыдущем примере — у нас есть:


  • Неизменный класс с тремя полями
  • Конструктор присваивает эти поля
  • Геттеры
  • equals(), hashCode() и toString()

Чтобы лучше это проиллюстрировать, давайте посмотрим на декомпилированную версию нашей записи.


public final class Cat extends java.lang.Record {
    private final java.lang.String name;
    private final int numberOfLives;
    private final java.lang.String color;

    public Cat(java.lang.String name, int numberOfLives, java.lang.String color) { /* compiled code */ }

    public java.lang.String toString() { /* compiled code */ }

    public final int hashCode() { /* compiled code */ }

    public final boolean equals(java.lang.Object o) { /* compiled code */ }

    public java.lang.String name() { /* compiled code */ }

    public int numberOfLives() { /* compiled code */ }

    public java.lang.String color() { /* compiled code */ }
}

Вы можете видеть, что код в значительной степени совпадает с нашим старым Cat. Заметным исключением является то, что методы получения для сгенерированных полей именуются не так, как обычно — вместо getColor() есть только color().


Также класс расширяет java.lang.Record.


Реализация equals() считает две записи равными, если они имеют одинаковый тип и имеют одинаковые значения. Реализация toString() печатает нашу запись следующим образом:


Cat[name=Fluffy, numberOfLives=9, color=White]

Хотя эти методы предоставляются автоматически, их можно переопределить, если это необходимо.


Ограничения


Существуют некоторые недостатки и ограничения записей, о которых вам следует знать.


  • Записи не могут расширять любой класс, хотя они могут реализовывать интерфейсы.
  • Записи не могут быть абстрактными.
  • Записи неявно являются final; они не могут быть унаследованы
  • Вы можете объявить дополнительные поля в теле записи, но только если они статичны

Добавление методов


Хотя записи в основном используются как простые носители данных, вы можете объявить свои собственные методы. Конечно, поскольку записи являются неизменяемыми, вы не можете изменять никакое состояние, но оно все равно может быть полезным. Например:


public record Cat(String name, int numberOfLives, String color) {

    public boolean isAlive() {
        return numberOfLives >= 0;
    }
}

Вы также можете добавить статические методы.


Пользовательские (Custom) конструкторы


По умолчанию новые записи содержат только конструктор, которому нужны все поля записи в качестве параметров. Например, наш класс Cat с тремя полями должна быть построен так:


Cat cat = new Cat("Fluffy", 9, "White");

Что, если некоторые параметры могут быть необязательными — если мы их не предоставляем, можно использовать некоторое значение по умолчанию.


В нашем случае число жизней для новых кошек, скорее всего, всегда будет равно 9. Мы можем создать дополнительный конструктор, который принимает только имя и цвет, а количество жизней может быть установлено равным 9 в качестве значения по умолчанию. Конечно, конструктор со всеми тремя полями все еще существует и доступен.


public record Cat(String name, int numberOfLives, String color) {

    public Cat(String name, String color) {
        this(name, 9, color);
    }
}

Конструктор со всеми полями генерируется для нас автоматически. Но иногда вам нужно выполнить там некоторую пользовательскую логику. Например, проверка ввода. Вы можете объявить самостоятельно, если вам необходимо, конструктор со всеми полями:


public record Cat(String name, int numberOfLives, String color) {

    public Cat(String name,int numberOfLives, String color) {
        if(numberOfLives < 0) {
            throw new IllegalArgumentException("Number of lives cannot be less than 0.");
        }

        if(numberOfLives > 9) {
            throw new IllegalArgumentException("Cats cannot have that many lives.");
        }

        this.name = name;
        this.numberOfLives = numberOfLives;
        this.color = color;
    }
}

Если вы переопределяете конструктор со всеми полями, указанными в записи (канонический конструктор), вы можете использовать объявление без записи параметров. Они все еще доступны для использования, но код будет короче.


public record Cat(String name, int numberOfLives, String color) {

    // This is the same as public Cat(String name, int numberOfLives, String color)
    public Cat {
        // name, numberOfLives and color available here
    }
}

Интроспекция во время выполнения


В java.lang.Class добавлены два новых метода, которые имеют функциональность, связанную с записями.


Первый называется isRecord(). Это довольно просто, вы можете просто проверить, является ли какой-то объект записью или нет:


Cat cat = new Cat("Fluffy", 9, "White");
if(cat.getClass().isRecord()) {
    //...
}

Другой является getRecordComponents(). Вы бы назвали это так же, как в примере выше. Возвращает список java.lang.reflect.RecordComponent. В основном это список всех полей, которые есть в записи с такой информацией, как:


  • Имя
  • Тип
  • Accessor
  • Аннотации

Попробуйте сами!


Если вы хотите попробовать эту функцию самостоятельно, вы уже можете это сделать, хотя Java 14 еще не выпущена (по состоянию на 2/2020).


Preview feature (Превью возможности)


Функциональность записей (Records) доступна в Java 14. Однако в настоящее время только в качестве предварительного релиза. Что это означает?


Превью возможности языка и VM — это новая функция платформы Java SE, которая полностью специфицирована, реализована, но в то же время определена как временная. Она включается в выпуск JDK для получения обратной связи с разработчиками на основе реального использования; это может привести к тому, что она станет постоянной в будущей платформе Java SE.

Перед следующим выпуском функции JDK будут проанализированы сильные и слабые стороны функции в реальном мире, чтобы решить, имеет ли функция долгосрочную роль в платформе Java SE и, если да, нуждается ли она в уточнении. Следовательно, функции могут быть предоставлены окончательный и постоянный статус (с уточнениями или без них), или пройти дополнительный период предварительного просмотра (с уточнениями или без них), либо они могут быть удалены.

Такие функции поставляются в JDK, но не включены по умолчанию. Вы должны явно разрешить им использовать их. Излишне говорить, что она предназначена не для производственного использования, а скорее для оценки и экспериментов, поскольку она может быть удалена или сильно изменена в будущем выпуске.


Чтобы попробовать эту функцию самостоятельно, вам нужно установить JDK 14.


Настройка IntelliJ IDEA


В IntelliJ IDEA вы можете включить функции Preview feature в меню File → Project Structure.



Чтобы использовать записи в IntelliJ IDEA, вам потребуется версия 2020.1 и более поздняя. По состоянию на 2/2020 она доступна как сборка по программе предварительного доступа (Early Access Program build). В настоящее время IDEA имеет базовую поддержку для записей, но полноценная поддержка должна быть доступна в релиз версии.


Ручная компиляция


Альтернативный вариант — вы собираете проект вручную. Тогда вам нужно предоставить следующие параметры для javac:


javac --release 14 --enable-preview ...

Это для компиляции. Во время выполнения вы просто предоставляете --enable-preview


java --enable-preview ...

Maven проекты


Для сборок Maven вы можете использовать следующую конфигурацию:


<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <release>14</release>
                <compilerArgs>
                    --enable-preview
                </compilerArgs>
```14</source>
                <target>14</target>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <argLine>--enable-preview</argLine>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <configuration>
                <argLine>--enable-preview</argLine>
            </configuration>
        </plugin>
    </plugins>
</build>
Теги:
Хабы:
+18
Комментарии29

Публикации

Истории

Работа

Java разработчик
358 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн