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 .