Java 14 Record Keyword

1. Einleitung

Das Übergeben unveränderlicher Daten zwischen Objekten ist eine der häufigsten, aber alltäglichsten Aufgaben in vielen Java-Anwendungen.

Vor Java 14 erforderte dies die Erstellung einer Klasse mit Boilerplate-Feldern und -Methoden, die für triviale Fehler und durcheinandergebrachte Absichten anfällig waren.

Mit der Veröffentlichung von Java 14 können wir jetzt Datensätze verwenden, um diese Probleme zu beheben.

In diesem Lernprogramm werden die Grundlagen von Datensätzen erläutert , einschließlich ihres Zwecks, der generierten Methoden und der Anpassungstechniken .

2. Zweck

In der Regel schreiben wir Klassen, um einfach Daten wie Datenbankergebnisse, Abfrageergebnisse oder Informationen von einem Dienst zu speichern.

In vielen Fällen sind diese Daten unveränderlich, da die Unveränderlichkeit die Gültigkeit der Daten ohne Synchronisation sicherstellt .

Um dies zu erreichen, erstellen wir Datenklassen mit den folgenden Eigenschaften:

  1. privates , letztes Feld für jedes Datenelement
  2. Getter für jedes Feld
  3. öffentlicher Konstruktor mit einem entsprechenden Argument für jedes Feld
  4. entspricht der Methode, die true für Objekte derselben Klasse zurückgibt , wenn alle Felder übereinstimmen
  5. hashCode- Methode, die denselben Wert zurückgibt, wenn alle Felder übereinstimmen
  6. toString- Methode, die den Namen der Klasse und den Namen jedes Felds sowie den entsprechenden Wert enthält

Zum Beispiel können wir eine einfache Personendatenklasse mit einem Namen und einer Adresse erstellen :

public class Person { private final String name; private final String address; public Person(String name, String address) { this.name = name; this.address = address; } @Override public int hashCode() { return Objects.hash(name, address); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof Person)) { return false; } else { Person other = (Person) obj; return Objects.equals(name, other.name) && Objects.equals(address, other.address); } } @Override public String toString() { return "Person [name=" + name + ", address=" + address + "]"; } // standard getters }

Während dies unser Ziel erreicht, gibt es zwei Probleme damit:

  1. Es gibt viel Boilerplate-Code
  2. Wir verschleiern den Zweck unserer Klasse - eine Person mit einem Namen und einer Adresse zu vertreten

Im ersten Fall müssen wir den gleichen langwierigen Prozess für jede Datenklasse wiederholen, monoton ein neues Feld für jedes Datenelement erstellen , die Methoden equals , hashCode und toString erstellen und einen Konstruktor erstellen, der jedes Feld akzeptiert.

IDEs können zwar viele dieser Klassen automatisch generieren, sie aktualisieren unsere Klassen jedoch nicht automatisch, wenn wir ein neues Feld hinzufügen . Wenn wir beispielsweise ein neues Feld hinzufügen, müssen wir unsere Methode equals aktualisieren , um dieses Feld aufzunehmen.

Im zweiten Fall verdeckt der zusätzliche Code, dass unsere Klasse einfach eine Datenklasse ist , die zwei Zeichenfolgenfelder enthält : Name und Adresse .

Ein besserer Ansatz wäre, explizit zu erklären, dass unsere Klasse eine Datenklasse ist.

3. Die Grundlagen

Ab JDK 14 können wir unsere sich wiederholenden Datenklassen durch Datensätze ersetzen. Datensätze sind unveränderliche Datenklassen, für die nur Typ und Name der Felder erforderlich sind.

Die Methoden equals , hashCode und toString sowie die privaten , letzten Felder und der öffentliche Konstruktor werden vom Java-Compiler generiert.

Um einen Personendatensatz zu erstellen , verwenden wir das Schlüsselwort record :

public record Person (String name, String address) {}

3.1. Konstrukteur

Mithilfe von Datensätzen wird für uns ein öffentlicher Konstruktor mit einem Argument für jedes Feld generiert.

Im Fall unseres Personendatensatzes lautet der entsprechende Konstruktor:

public Person(String name, String address) { this.name = name; this.address = address; }

Dieser Konstruktor kann auf dieselbe Weise wie eine Klasse verwendet werden, um Objekte aus dem Datensatz zu instanziieren:

Person person = new Person("John Doe", "100 Linda Ln.");

3.2. Getters

Wir erhalten außerdem kostenlos öffentliche Getter-Methoden, deren Namen mit dem Namen unseres Feldes übereinstimmen.

In unserem Personendatensatz bedeutet dies einen Namen () und eine Adresse () :

@Test public void givenValidNameAndAddress_whenGetNameAndAddress_thenExpectedValuesReturned() { String name = "John Doe"; String address = "100 Linda Ln."; Person person = new Person(name, address); assertEquals(name, person.name()); assertEquals(address, person.address()); }

3.3. gleich

Zusätzlich wird für uns eine Equals- Methode generiert.

Diese Methode gibt true zurück , wenn das angegebene Objekt vom gleichen Typ ist und die Werte aller Felder übereinstimmen:

@Test public void givenSameNameAndAddress_whenEquals_thenPersonsEqual() { String name = "John Doe"; String address = "100 Linda Ln."; Person person1 = new Person(name, address); Person person2 = new Person(name, address); assertTrue(person1.equals(person2)); }

Wenn sich eines der Felder zwischen zwei Personeninstanzen unterscheidet , gibt die Methode equals false zurück .

3.4. Hash-Code

Ähnlich wie bei unserer Methode equals wird auch für uns eine entsprechende hashCode- Methode generiert.

Unsere hashCode- Methode gibt denselben Wert für zwei Personenobjekte zurück , wenn alle Feldwerte für beide Objekte übereinstimmen (außer Kollisionen aufgrund des Geburtstagsparadoxons) :

@Test public void givenSameNameAndAddress_whenHashCode_thenPersonsEqual() { String name = "John Doe"; String address = "100 Linda Ln."; Person person1 = new Person(name, address); Person person2 = new Person(name, address); assertEquals(person1.hashCode(), person2.hashCode()); } 

Der hashCode- Wert unterscheidet sich, wenn sich einer der Feldwerte unterscheidet.

3.5. toString

Lastly, we also receive atoString method that results in a string containing the name of the record, followed by the name of each field and its corresponding value in square brackets.

Therefore, instantiating a Person with a name of “John Doe” and an address of “100 Linda Ln.” results in the following toString result:

Person[name=John Doe, address=100 Linda Ln.]

4. Constructors

While a public constructor is generated for us, we can still customize our constructor implementation.

This customization is intended to be used for validation and should be kept as simple as possible.

For example, we can ensure that the name and address provided to our Person record are not null using the following constructor implementation:

public record Person(String name, String address) { public Person { Objects.requireNonNull(name); Objects.requireNonNull(address); } }

We can also create new constructors with different arguments by supplying a different argument list:

public record Person(String name, String address) { public Person(String name) { this(name, "Unknown"); } }

As with class constructors, the fields can be referenced using the this keyword (for example, this.name and this.address) and the arguments match the name of the fields (that is, name and address).

Note that creating a constructor with the same arguments as the generated public constructor is valid, but this requires that each field be manually initialized:

public record Person(String name, String address) { public Person(String name, String address) { this.name = name; this.address = address; } }

Additionally, declaring a no-argument constructor and one with an argument list matching the generated constructor results in a compilation error.

Therefore, the following will not compile:

public record Person(String name, String address) { public Person { Objects.requireNonNull(name); Objects.requireNonNull(address); } public Person(String name, String address) { this.name = name; this.address = address; } }

5. Static Variables & Methods

As with regular Java classes, we can also include static variables and methods in our records.

We declare static variables using the same syntax as a class:

public record Person(String name, String address) { public static String UNKNOWN_ADDRESS = "Unknown"; }

Likewise, we declare static methods using the same syntax as a class:

public record Person(String name, String address) { public static Person unnamed(String address) { return new Person("Unnamed", address); } }

We can then reference both static variables and static methods using the name of the record:

Person.UNKNOWN_ADDRESS Person.unnamed("100 Linda Ln.");

6. Conclusion

In diesem Artikel haben wir uns mit dem in Java 14 eingeführten Schlüsselwort record befasst , einschließlich der grundlegenden Konzepte und Feinheiten.

Mithilfe von Datensätzen - mit ihren vom Compiler generierten Methoden - können wir den Boilerplate-Code reduzieren und die Zuverlässigkeit unserer unveränderlichen Klassen verbessern.

Der Code und die Beispiele für dieses Tutorial finden Sie auf GitHub.