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

Введение в Immutables

1. Вступление

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

Immutables состоит из аннотаций и процессоров аннотаций для генерации и работы с сериализуемыми и настраиваемыми неизменяемыми объектами.

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

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


    org.immutables
    value
    2.2.10
    provided

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

Последнюю версию библиотеки можно найтиhere.

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

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

Ключ к достижению этого - правильное использование аннотации@Value.Immutable. It generates an immutable version of an annotated type and prefixes its name with the Immutable keyword.

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

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

3.1. Использование@Value.Immutable сAbstract Classes иInterfaces

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

@Value.Immutable
public abstract class Person {

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

}

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

@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. Обратите внимание, что сгенерированный конструктор имеет доступprivate.

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

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

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

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

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

ImmutablePerson john43 = john.withAge(43);

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

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

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

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

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();

}

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

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();

}

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

@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() возвращает предварительно вычисленныйhashcode, сгенерированный при создании объекта.

5. Заключение

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

Весь исходный код и модульные тесты в статье можно найти в папкеGitHub repository.