Перейти к основному содержимому

Введение в неизменяемые

· 5 мин. чтения

1. Введение

В этой статье мы покажем, как работать с библиотекой Immutables .

Библиотека состоит из аннотаций и обработчиков аннотаций для создания сериализуемых и настраиваемых неизменяемых объектов и работы с ними.

2. Зависимости Maven

Чтобы использовать Immutables в своем проекте, вам необходимо добавить следующую зависимость в раздел зависимостей вашего файла pom.xml :

<dependency>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
<version>2.2.10</version>
<scope>provided</scope>
</dependency>

Поскольку этот артефакт не требуется во время выполнения, рекомендуется указать предоставленную область.

Новейшую версию библиотеки можно найти здесь .

3. Неизменяемые

Библиотека генерирует неизменяемые объекты из абстрактных типов: Interface , Class , Annotation .

Ключом к достижению этого является правильное использование аннотации @Value.Immutable . Он создает неизменяемую версию аннотированного типа и добавляет к его имени ключевое слово Immutable .

Если мы попытаемся сгенерировать неизменяемую версию класса с именем « X », будет сгенерирован класс с именем «ImmutableX». Сгенерированные классы не являются рекурсивно-неизменяемыми, поэтому следует помнить об этом.

И небольшое замечание: поскольку Immutables использует обработку аннотаций, вам нужно не забыть включить обработку аннотаций в вашей среде IDE.

3.1. Использование @Value.Immutable с абстрактными классами и интерфейсами

Давайте создадим простой абстрактный класс Person , состоящий из двух абстрактных методов доступа, представляющих поля, которые нужно сгенерировать, а затем аннотируем класс аннотацией @Value.Immutable :

@Value.Immutable
public abstract class Person {

abstract String getName();
abstract Integer getAge();

}

После обработки аннотации мы можем найти готовый к использованию, только что сгенерированный класс ImmutablePerson в каталоге target/generated-sources :

@Generated({"Immutables.generator", "Person"})
public final class ImmutablePerson extends Person {

private final String name;
private final Integer age;

private ImmutablePerson(String name, Integer age) {
this.name = name;
this.age = age;
}

@Override
String getName() {
return name;
}

@Override
Integer getAge() {
return age;
}

// toString, hashcode, equals, copyOf and Builder omitted

}

Сгенерированный класс поставляется с реализованными методами toString , hashcode , equals и пошаговым конструктором ImmutablePerson.Builder . Обратите внимание, что сгенерированный конструктор имеет закрытый доступ.

Чтобы создать экземпляр класса ImmutablePerson , нам нужно использовать конструктор или статический метод ImmutablePerson.copyOf, который может создать копию ImmutablePerson из объекта Person .

Если мы хотим создать экземпляр с помощью компоновщика, мы можем просто написать код:

ImmutablePerson john = ImmutablePerson.builder()
.age(42)
.name("John")
.build();

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

Давайте обновим возраст Джона и создадим новый объект john43 :

ImmutablePerson john43 = john.withAge(43);

В таком случае будут верны следующие утверждения:

assertThat(john).isNotSameAs(john43);
assertThat(john.getAge()).isEqualTo(42);

4. Дополнительные утилиты

Такая генерация классов не была бы очень полезной без возможности ее настройки. Библиотека Immutables поставляется с набором дополнительных аннотаций, которые можно использовать для настройки вывода @Value.Immutable . Чтобы увидеть их все, обратитесь к документации Immutables .

4.1. Аннотация @ Value.Parameter

Аннотацию @Value.Parameter можно использовать для указания полей, для которых должен быть сгенерирован метод-конструктор.

Если вы аннотируете свой класс следующим образом:

@Value.Immutable
public abstract class Person {

@Value.Parameter
abstract String getName();

@Value.Parameter
abstract Integer getAge();
}

Его можно будет создать следующим образом:

ImmutablePerson.of("John", 42);

4.2. Аннотация @ Value.Default

Аннотация @Value.Default позволяет указать значение по умолчанию, которое следует использовать, когда начальное значение не указано. Для этого вам нужно создать неабстрактный метод доступа, возвращающий фиксированное значение, и аннотировать его с помощью @Value.Default :

@Value.Immutable
public abstract class Person {

abstract String getName();

@Value.Default
Integer getAge() {
return 42;
}
}

Будет верным следующее утверждение:

ImmutablePerson john = ImmutablePerson.builder()
.name("John")
.build();

assertThat(john.getAge()).isEqualTo(42);

4.3. Аннотация @ Value.Auxiliary

Аннотацию @Value.Auxiliary можно использовать для аннотации свойства, которое будет храниться в экземпляре объекта, но будет игнорироваться реализациями equals , hashCode и toString .

Если вы аннотируете свой класс следующим образом:

@Value.Immutable
public abstract class Person {

abstract String getName();
abstract Integer getAge();

@Value.Auxiliary
abstract String getAuxiliaryField();

}

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

ImmutablePerson john1 = ImmutablePerson.builder()
.name("John")
.age(42)
.auxiliaryField("Value1")
.build();

ImmutablePerson john2 = ImmutablePerson.builder()
.name("John")
.age(42)
.auxiliaryField("Value2")
.build();
assertThat(john1.equals(john2)).isTrue();
assertThat(john1.toString()).isEqualTo(john2.toString());
assertThat(john1.hashCode()).isEqualTo(john2.hashCode());

4.4. Аннотация @ Value.Immutable (Prehash = True)

Поскольку наши сгенерированные классы неизменяемы и никогда не могут быть изменены, результаты hashCode всегда будут оставаться одинаковыми и могут быть вычислены только один раз во время создания экземпляра объекта.

Если вы аннотируете свой класс следующим образом:

@Value.Immutable(prehash = true)
public abstract class Person {

abstract String getName();
abstract Integer getAge();

}

При проверке сгенерированного класса вы можете увидеть, что значение хэш -кода теперь предварительно вычислено и сохранено в поле:

@Generated({"Immutables.generator", "Person"})
public final class ImmutablePerson extends Person {

private final String name;
private final Integer age;
private final int hashCode;

private ImmutablePerson(String name, Integer age) {
this.name = name;
this.age = age;
this.hashCode = computeHashCode();
}

// generated methods

@Override
public int hashCode() {
return hashCode;
}
}

Метод hashCode() возвращает предварительно вычисленный хэш -код , сгенерированный при создании объекта.

5. Вывод

В этом кратком руководстве мы показали основные принципы работы библиотеки Immutables .

Весь исходный код и юнит-тесты в статье можно найти в репозитории GitHub .