Introdução aos Imutáveis

Introdução aos Imutáveis

1. Introdução

Neste artigo iremos mostrar como trabalhar com a bibliotecaImmutables.

O Immutables consiste em anotações e processadores de anotação para gerar e trabalhar com objetos imutáveis ​​serializáveis ​​e personalizáveis.

2. Dependências do Maven

Para usar Imutáveis ​​em seu projeto, você precisa adicionar a seguinte dependência à seçãodependencies do arquivopom.xml:


    org.immutables
    value
    2.2.10
    provided

Como este artefato não é necessário durante o tempo de execução, é aconselhável especificar o escopoprovided.

A versão mais recente da biblioteca pode ser encontradahere.

3. Imutáveis

A biblioteca gera objetos imutáveis ​​de tipos abstratos:Interface,Class,Annotation.

A chave para conseguir isso é o uso adequado da anotação@Value.Immutable. It generates an immutable version of an annotated type and prefixes its name with the Immutable keyword.

Se tentarmos gerar uma versão imutável da classe chamada “X“, ela irá gerar uma classe chamada“ImmutableX”. As classes geradas não são recursivamente imutáveis, então é bom manter isso em mente.

E uma observação rápida - como o Immutables utiliza o processamento de anotações, lembre-se de habilitar o processamento de anotações no seu IDE.

3.1. Usando@Value.Immutable comAbstract Classes eInterfaces

Vamos criar uma classeabstract simplesPerson consistindo em dois métodos acessadoresabstract que representam os campos a serem gerados e, em seguida, anotar a classe com a anotação@Value.Immutable:

@Value.Immutable
public abstract class Person {

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

}

Depois que o processamento da anotação for concluído, podemos encontrar umnewly-generated ImmutablePerson class in a target/generated-sources directory pronto para uso:

@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

}

A classe gerada vem com os métodostoString,hashcode,equals implementados e com um stepbuilderImmutablePerson.Builder. Observe que o construtor gerado tem acessoprivate.

Para construir uma instância da classeImmutablePerson, precisamos usar o construtor ou método estáticoImmutablePerson.copyOf,, que pode criar uma cópiaImmutablePerson de um objetoPerson.

Se queremos construir uma instância usando o construtor, podemos simplesmente codificar:

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

As classes geradas são imutáveis, o que significa que não podem ser modificadas. Se você deseja modificar um objeto já existente, pode usar um dos métodos “withX”, que não modifica um objeto original, mas cria uma nova instância com um campo modificado.

Vamos atualizarjohn’s idade e criar um novo objetojohn43:

ImmutablePerson john43 = john.withAge(43);

Nesse caso, as seguintes afirmações serão verdadeiras:

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

4. Utilitários Adicionais

Essa geração de classe não seria muito útil sem poder personalizá-la. A biblioteca Immutables vem com um conjunto de anotações adicionais que podem ser usadas para personalizar a saída de@Value.Immutable. Para ver todos eles, consulte Imutáveis ​​'documentation.

4.1. A anotação@Value.Parameter

A anotação@Value.Parameter pode ser usada para especificar campos, para os quais o método do construtor deve ser gerado.

Se você anotar sua turma assim:

@Value.Immutable
public abstract class Person {

    @Value.Parameter
    abstract String getName();

    @Value.Parameter
    abstract Integer getAge();
}

Será possível instanciar da seguinte maneira:

ImmutablePerson.of("John", 42);

4.2. A anotação@Value.Default

A anotação@Value.Default permite que você especifique um valor padrão que deve ser usado quando um valor inicial não é fornecido. Para fazer isso, você precisa criar um método de acesso não abstrato retornando um valor fixo e anotá-lo com@Value.Default:

@Value.Immutable
public abstract class Person {

    abstract String getName();

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

A seguinte afirmação será verdadeira:

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

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

4.3. A anotação@Value.Auxiliary

A anotação@Value.Auxiliary pode ser usada para anotar uma propriedade que será armazenada na instância de um objeto, mas será ignorada pelas implementaçõesequals,hashCodeetoString.

Se você anotar sua turma assim:

@Value.Immutable
public abstract class Person {

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

    @Value.Auxiliary
    abstract String getAuxiliaryField();

}

As seguintes afirmações serão verdadeiras ao usar o campoauxiliary:

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. A anotação@Value.Immutable(Prehash = True)

Visto que nossas classes geradas são imutáveis ​​e nunca podem ser modificadas, os resultados dehashCode sempre permanecerão os mesmos e podem ser calculados apenas uma vez durante a instanciação do objeto.

Se você anotar sua turma assim:

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

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

}

Ao inspecionar a classe gerada, você pode ver que o valorhashcode agora é pré-computado e armazenado em um campo:

@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;
    }
}

O métodohashCode() retorna umhashcode pré-calculado gerado quando o objeto foi construído.

5. Conclusão

Neste tutorial rápido, mostramos o funcionamento básico da bibliotecaImmutables.

Todo o código fonte e testes de unidade no artigo podem ser encontrados emGitHub repository.