Java 17: Records: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
|||
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt) | |||
Zeile 51: | Zeile 51: | ||
mit zwei {{java|double}}-Werten die der Reihenfolge der Deklaration entsprechen: | mit zwei {{java|double}}-Werten die der Reihenfolge der Deklaration entsprechen: | ||
{{java|code= | {{java|code= | ||
public Complex(double real, double img) { | |||
this.real = real; | this.real = real; | ||
this.img = img; | this.img = img; | ||
} | |||
}} | }} | ||
Jetzt können wir Methoden implementieren um mit den Zahlen zu rechnen -- etwa die Addition: | Jetzt können wir Methoden implementieren um mit den Zahlen zu rechnen -- etwa die Addition: | ||
Zeile 105: | Zeile 105: | ||
public record EmailAdress(String adr) { | public record EmailAdress(String adr) { | ||
public EmailAdress(String adr) { | public EmailAdress(String adr) { | ||
if (adr == null | if (!(adr == null && adr.contains("@"))); | ||
throw new RuntimeException("not an eMail address: " + adr); | throw new RuntimeException("not an eMail address: " + adr); | ||
} | } |
Aktuelle Version vom 21. Oktober 2023, 20:00 Uhr
Records
Der record
ist eine neue Java-Komponente die auf einer Ebene liegt mit Klassen und Interfaces.
Man kann ihn beschreiben als eine nicht ableitbare Klasse unveränderlicher (immutable) Objekte.
Das schöne am record
ist, daß er eine ganze Reihe von default-Implementierungen bietet,
ohne daß man den ganzen boiler plate code dazu tippen muß.
Die Deklaration erfolgt nicht mit dem Keyword class
, sondern mit dem context keyword record
.
Hier ist eine ganz einfache Deklaration:
public record Kilometer(double dist) {};
Was bietet uns der record Kilometer
?
- Ein
Kilometer
-Objekt hat ein einziges Felddist
from Typdouble
- Objekte werden erzeugt über
new Kilometer(42.195)
- Auf den Wert wird zugegriffen über den Getter
dist()
- Die Methode
toString()
liefert etwas wie "Kilometer [dist=42.195]" - Die Methoden
equals
undhashCode
werden -- gemäß Kontrakt implementiert
All' das erzeugt der Compiler automatisch. Natürlich ist die Erzeugungung von records mit beliebig vielen Feldern möglich, weiter unten ist eine Beispiel mit zwei Feldern geschrieben.
Was kann man nun mit dem record
anstellen?
Neben der offensichtlichen Möglichkeit, damit Data-Container ohne großen Overhead zu generieren,
Kann man damit auch eigene Datentypen definieren. Denn ein record
kann -- wie jede andere Klasse auch --
statische und nicht-statische Methoden enthalten. Das Einzige was nicht erlaubt ist, ist die Definition von nicht-statischen Feldern.
Mit anderen Worten:
Der Status eines record-Objektes wird durch die initiale Befüllung der implizit definierten Felder bestimmt und ändert sich niemals.
Nur syntaktischer Zucker?
Records lassen uns also eine ganze Menge Code sparen, aber strenggenommen können wir damit nicht mehr machen als mit klassischen Klassen bereits möglich war. Lohnt sich die Erweiterung dann überhaupt
Tatsächlich sind Records zunächst Java-Klassen, allerdings haben sie ein eigenes Flag das sie für die JVM von normalen Klassen unterscheidbar macht. Sie werden von der JVM anders behandelt, sind effizienter und können beim Optimieren der JVM noch besser gemacht werden.
Komplexe Zahlen
Ein ganz hübsches Beispiel ist die Definition komplexer Zahlen. Die Deklaration sieht zunächst so aus:
public record Complex(double real, double img) { }
Der reale Anteil wird mit real
bezeichnet, der imaginäre -- wir sind tippfaul -- mit img
.
Sie werden mit den Gettern real()
und img()
angefragt. Der Compiler erzeugt uns einen Konstruktor
mit zwei double
-Werten die der Reihenfolge der Deklaration entsprechen:
public Complex(double real, double img) { this.real = real; this.img = img; }
Jetzt können wir Methoden implementieren um mit den Zahlen zu rechnen -- etwa die Addition:
public Complex plus(Complex b) { return new Complex(this.real + b.real, this.img + b.img); }
Da record-Objekte nicht verändert werden können, wird bei der Addition jedesmal ein neues Objekt erzeugt.
Das ist typisch für immutable objects und entspricht auch der Implementierung von Klassen wie LocalDate
.
Die Additions-Methode lternativ könnte man auch statisch definieren:
public static Complex plus(Complex a, Complex b) { return new Complex(a.real + b.real, a.img + b.img); }
Will man keine extra-Methode für die Ausgabe implementieren, kann man für eine hübschere Ausgabe die toString
-Methode überschreiben:
@Override public String toString() { return real + " + " + img + "i"; }
Warum ist das cool? Mit herkömmlichen Klassen ist das schon seit Java 1.1 möglich?
- Der Code wird sehr übersichtlich, weil Java uns eine ganze Menge abnimmt
- Die Objekte sind immutable, ohne daß dafür irgendwelche Maßnahmen erforderlich wären
- Die Java-VM kann die Verarbeitung der records in diesem Sinne optimieren
Den kompletten Code giebt's hier.
Integrierte Validierung
Um eine eMail-Adresse zu speichern verwenden die meisten Entwickler den Java-Typ String
.
Das ist grundsätzlich verkehrt, wenn man die die Zeichenkette "balfasel" betrachtet, denn das ist definitiv keine keine gültige eMail-Adresse.
Weil ein String immer gegene einen beliebigen anderen String ausgetauscht werden kann, muß der String vor jeder Verwednung daraufhin geprüft werden ob er tatsächlich eine gültige eMail-Adresse enthält. Stellen wir uns eine Anwendung vor die ganz am Anfang eine eMail-Adresse beschafft und dann durch viele Schichten durchreicht bevor sie denn endlich irgendwann verwendet wird. Wenn sich dann tatsächlich herausstellt, daß die eMail-Adresse korrupt ist, was macht man dann mit dem Fehler?
Der objektorientierte Weg ist -- auch hier -- den String in einer eigenen Klasse zu kapseln. Dazu hat keine Entwickler lust. Niemand mag den ganzen Code schreiben, niemand mag die mögliche Performance-Einbuße in kauf nehmen.
Java 17 diesen Ausreden mit dem record
ein Ende.
Folgende einfache Impementierung verdeutlicht die Idee
public record EmailAdress(String adr) { public EmailAdress(String adr) { if (!(adr == null && adr.contains("@"))); throw new RuntimeException("not an eMail address: " + adr); } this.adr = adr; } }
Genau wie der default-Konstruktor kann auch der für den record
vom Compiler automatisch erzeugte Konstruktur
durch einen Selbstgeschriebenen ersetzt werden. Im obigen Beispiel wird das record
-Objekt nur dann erzeugt,
wenn der als eMail-Adresse angebotene String eine Affenschaukel enthält.
Der Beispiel-Code ist auf GitHub zu finden.