Immutable Objects in Java 14 neu gemacht

Unter einem immutable Object versteht man eines, bei dem der innere Status nach der initialen Erzeugung nicht mehr verändert werden kann. Wir wollen uns in diesem Beitrag ansehen, welche Möglichkeiten es für die technische Umsetzung solcher unveränderlichen Objekte in Java gibt, und welche Neuerungen dazu Java 14 zu bieten hat.

Variante 1

Bei der klassischen Lösung werden alle Felder mit final ausgezeichnet. Das Setzen des endgültigen Status eines Objekts erfolgt über einen der Konstruktoren. Auf jegliche Setter muss verzichtet werden, solch eine Klasse darf nur Getter besitzen. Der Vollständigkeit halber implementieren wir auch eine toString-Methode. Das Ganze sieht dann für eine Klasse Person (id, firstName, lastName, birthDate, vip) wie folgt aus:

public class Person {
  private final Long id;
  private final String firstName;
  private final String lastName;
  private final LocalDate birthDate;
  private final boolean vip;

  public Person(Long id, String firstName, String lastName, LocalDate birthDate, boolean vip) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.birthDate = birthDate;
    this.vip = vip;
  }

  public Person(String firstName, String lastName) {
    this(null, firstName, lastName, null, false);
  }

  public Long getId() {
    return id;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public LocalDate getBirthDate() {
    return birthDate;
  }

  public boolean isVip() {
    return vip;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName +

           ", birthDate=" + birthdate + ", vip=" + vip + "]";
  }
}

Wie sich diese Klasse nun nutzen lässt, demonstriert der folgende Quellcode:

var person = new Person(1L, "Arnold", "Schwarzenegger", LocalDate.of(1947, 7, 30), true);

System.out.println(
  person.getFirstName() + " " + person.getLastName()
);

Variante 2

Bei den nächsten beiden Varianten setzen wir Lombok ein. Diese äußerst nützliche Bibliothek sollte jedem professionellen Java-Entwickler bekannt sein. Damit lässt sich rein durch die Verwendung von Annotationen ein funktional identer, aber deutlich kürzerer Quellcode erreichen. Beginnen wir mit der klassischen Lombok-Lösung:

@AllArgsConstructor
@Getter
@ToString
public class Person {
  private final Long id;
  private final String firstName;
  private final String lastName;
  private final LocalDate birthDate;
  private final boolean vip;

  public Person(String firstName, String lastName) {
    this(null, firstName, lastName, null, false);
  }
}

Variante 3

Seit dem letzten Lombok-Release geht das dank der neuen Annotation @Value noch einfacher. Sie macht automatisch jedes Feld private und final. Auch erzeugt sie Getter und implementiert auf klassische Art und Weise die Object-Methoden equals, hashCode und toString. Weist eine Klasse keinen Konstruktor auf, generiert @Value automatischen einen vollständigen Konstruktor.

@Value
@AllArgsConstructor
public class Person {
  Long id;
  String firstName;
  String lastName;
  LocalDate birthDate;
  boolean vip;

  public Person(String firstName, String lastName) {
    this(null, firstName, lastName, null, false);
  }
}

Variante 4

Bei der letzten Variante setzen wir auf ein neues Sprachkonzept von Java 14: Records. Entwickelt wurden sie im Rahmen des Projekts Valhalla. Vergleichbar sind Records mit den Datenklassen in Kotlin. Damit lässt sich die Person von oben nun wie folgt modellieren:

public record Person(Long id, String firstName, String lastName, LocalDate birthDate, boolean vip) {
  public Person(String firstName, String lastName) {
    this(null, firstName, lastName, null, false);
  }
}

Zu beachten ist, dass Records keine klassischen Getter generieren, sondern für jedes Feld x eine Zugriffsmethode x() erzeugen. Dadurch verändert sich der Quellcode, der die Anwendung demonstriert, wie folgt:

var person = new Person(1L, "Arnold", "Schwarzenegger", LocalDate.of(1947, 7, 30), true);

System.out.println(
  person.firstName() + " " + person.lastName()
);

Der Beitrag stellte vier Arten vor, wie sich Immutable Objects in Java erzeugen lassen. Wie sich zeigte, lassen sich diese durch immer kürzeren Quellcode implementieren.

Die kontinuierliche Weiterentwicklung von Java ist in vollem Gange. Jedes halbe Jahr kommt eine neue Version heraus. In unseren Schulungen gehen wir auf diese Änderungen ein.

 

Bernhard Löwenstein

01.07.2020