Einführung in unveränderliche Dinge

Einführung in Immutables

1. Einführung

In diesem Artikel zeigen wir, wie man mit derImmutables-Bibliothek arbeitet.

Immutables besteht aus Annotationen und Annotationsprozessoren zum Generieren und Arbeiten mit serialisierbaren und anpassbaren unveränderlichen Objekten.

2. Maven-Abhängigkeiten

Um Immutables in Ihrem Projekt verwenden zu können, müssen Sie dem Abschnittdependencies Ihrerpom.xml-Datei die folgende Abhängigkeit hinzufügen:


    org.immutables
    value
    2.2.10
    provided

Da dieses Artefakt zur Laufzeit nicht erforderlich ist, ist es ratsam, den Bereich vonprovidedanzugeben.

Die neueste Version der Bibliothek finden Sie unterhere.

3. Unveränderliche

Die Bibliothek generiert unveränderliche Objekte aus abstrakten Typen:Interface,Class,Annotation.

Der Schlüssel, um dies zu erreichen, ist die ordnungsgemäße Verwendung der Annotation von@Value.Immutable. It generates an immutable version of an annotated type and prefixes its name with the Immutable keyword.

Wenn wir versuchen, eine unveränderliche Version der Klasse mit dem Namen "X" zu generieren, wird eine Klasse mit dem Namen "“ImmutableX”." generiert. Generierte Klassen sind nicht rekursiv unveränderlich. Denken Sie also daran.

Und eine kurze Anmerkung: Da Immutables die Anmerkungsverarbeitung verwendet, müssen Sie daran denken, die Anmerkungsverarbeitung in Ihrer IDE zu aktivieren.

3.1. Verwenden von@Value.Immutable MitAbstract Classes undInterfaces

Erstellen wir eine einfacheabstract-KlassePerson, die aus zweiabstract-Zugriffsmethoden besteht, die die zu generierenden Felder darstellen, und kommentieren Sie die Klasse dann mit der@Value.Immutable-Annotation:

@Value.Immutable
public abstract class Person {

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

}

Nachdem die Annotationsverarbeitung abgeschlossen ist, können wir ein gebrauchsfertigesnewly-generated ImmutablePerson class in a target/generated-sources directory finden:

@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

}

Die generierte Klasse wird mit implementierten MethodentoString,hashcode,equals und einem StepbuilderImmutablePerson.Builder geliefert. Beachten Sie, dass der generierte KonstruktorprivateZugriff hat.

Um eine Instanz der KlasseImmutablePersonzu erstellen, müssen wir den Builder oder die statische MethodeImmutablePerson.copyOf,verwenden, mit der eine Kopie vonImmutablePersonaus einem Objekt vonPersonerstellt werden kann.

Wenn wir eine Instanz mit dem Builder erstellen möchten, können wir einfach Folgendes codieren:

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

Generierte Klassen sind unveränderlich, dh sie können nicht geändert werden. Wenn Sie ein bereits vorhandenes Objekt ändern möchten, können Sie eine der Methoden "withX" verwenden, mit der kein ursprüngliches Objekt geändert, sondern eine neue Instanz mit einem geänderten Feld erstellt wird.

Aktualisieren Sie das Alter vonjohn’sund erstellen Sie ein neuesjohn43-Objekt:

ImmutablePerson john43 = john.withAge(43);

In diesem Fall gelten die folgenden Aussagen:

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

4. Zusätzliche Dienstprogramme

Eine solche Klassengenerierung wäre nicht sehr nützlich, ohne sie anpassen zu können. Die Immutables-Bibliothek enthält eine Reihe zusätzlicher Anmerkungen, mit denen die Ausgabe von@Value.Immutableangepasst werden kann. Um alle zu sehen, beziehen Sie sich bitte aufdocumentationvon Immutables.

4.1. Die Annotation@Value.Parameter

Die Annotation@Value.Parameter kann zum Angeben von Feldern verwendet werden, für die eine Konstruktormethode generiert werden soll.

Wenn Sie Ihre Klasse folgendermaßen kommentieren:

@Value.Immutable
public abstract class Person {

    @Value.Parameter
    abstract String getName();

    @Value.Parameter
    abstract Integer getAge();
}

Es kann auf folgende Weise instanziiert werden:

ImmutablePerson.of("John", 42);

4.2. Die Annotation@Value.Default

Mit der Annotation@Value.Defaultkönnen Sie einen Standardwert angeben, der verwendet werden soll, wenn kein Anfangswert angegeben wird. Dazu müssen Sie eine nicht abstrakte Zugriffsmethode erstellen, die einen festen Wert zurückgibt, und ihn mit@Value.Default versehen:

@Value.Immutable
public abstract class Person {

    abstract String getName();

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

Die folgende Behauptung wird wahr sein:

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

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

4.3. Die Annotation@Value.Auxiliary

Die Annotation@Value.Auxiliary kann zum Annotieren einer Eigenschaft verwendet werden, die in der Instanz eines Objekts gespeichert wird, jedoch von den Implementierungenequals,hashCode undtoStringignoriert wird.

Wenn Sie Ihre Klasse folgendermaßen kommentieren:

@Value.Immutable
public abstract class Person {

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

    @Value.Auxiliary
    abstract String getAuxiliaryField();

}

Die folgenden Aussagen sind wahr, wenn das Feldauxiliaryverwendet wird:

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. Die Annotation@Value.Immutable(Prehash = True)

Da unsere generierten Klassen unveränderlich sind und niemals geändert werden können, bleiben die Ergebnisse vonhashCodeimmer gleich und können während der Instanziierung des Objekts nur einmal berechnet werden.

Wenn Sie Ihre Klasse folgendermaßen kommentieren:

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

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

}

Wenn Sie die generierte Klasse untersuchen, sehen Sie, dass der Wert vonhashcodejetzt vorberechnet und in einem Feld gespeichert wird:

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

Die MethodehashCode() gibt eine vorberechnetehashcode zurück, die beim Erstellen des Objekts generiert wurde.

5. Fazit

In diesem kurzen Tutorial haben wir die grundlegenden Funktionen derImmutables-Bibliothek gezeigt.

Alle Quellcode- und Komponententests im Artikel finden Sie inGitHub repository.