Der Spickzettel zum Seminar Java SE Fortgeschrittene Sprachfeatures — für
den Moment, in dem der Compiler eine Wildcard ablehnt, capture#1 of ? in der
Meldung steht oder die Frage aufkommt, welche Retention es noch mal brauchte.
Er sammelt die Regeln, nicht die Herleitung: Warum sie so sind und wie man
damit modelliert, ist die Leistung des Seminars. Maßgeblich bleibt die
Java-Sprachspezifikation. Jeder Abschnitt verweist auf sein Modul.
Records: was der Compiler erzeugt
Erzeugt wird
Eigenschaft
Ein Feld je Komponente
private und final
Der kanonische Konstruktor
Parameter = Komponenten in gleicher Reihenfolge
Je eine Zugriffsmethode
heißt wie die Komponente, ohne get-Präfix
equals und hashCode
vergleichen bzw. verrechnen alle Komponenten
toString
listet alle Komponenten mit Werten auf
Regel für Konstruktoren
Folge im Alltag
Nur der kanonische Konstruktor beschreibt Felder
Jede Prüfung gehört genau dorthin
Zusatzkonstruktoren beginnen mit this(...)
Vorbelegungen laufen immer durch die Prüfung
Sichtbarkeit nicht enger als der Record selbst
Public Record heißt public Konstruktor
Kompaktform ohne Parameterliste
Zuweisungen ergänzt der Compiler
public record Artikel(String nummer, List<String> etiketten) { public Artikel { // kompakte Form if (nummer == null || nummer.isBlank()) throw new IllegalArgumentException("nummer"); etiketten = List.copyOf(etiketten); // Parameter, nicht Feld }}
In der Kompaktform wird dem Parameter zugewiesen, nicht dem Feld — die
Zuweisung ans final-Feld ergänzt der Compiler am Ende. Records sind nur flach
unveränderlich: Eine Komponente vom Typ ArrayList bleibt von außen änderbar,
solange keine defensive Kopie erfolgt. Erlaubt sind eigene Methoden und
statische Felder, weitere Instanzfelder nicht.
return switch (position) { case Artikelposition(_, _, int menge, BigDecimal preis) -> preis.multiply(BigDecimal.valueOf(menge)); case Frachtzuschlag(_, BigDecimal betrag) -> betrag;};
Kein default-Zweig: Bei einem versiegelten Typ prüft der Compiler die
Vollständigkeit, und ein Sammelzweig schaltet genau diese Prüfung ab. Record-Muster
zerlegen die Variante beim Treffen; _ steht für Nicht-Gebrauchtes.
valueOf findet nur über den exakten Namen — für gebundene Kürzel oder
Zahlencodes braucht es eine eigene statische Suchmethode.
Rückgabe bei unbekanntem Wert
Passt, wenn
Kostet
Ausnahme werfen
Daten müssen gültig sein
Aufrufer braucht Fehlerbehandlung
Optional liefern
Fehlen ist normal
Aufrufer muss auspacken
Vorgabewert liefern
Es gibt einen sinnvollen
Fehler bleiben unsichtbar
null liefern
selten sinnvoll
Null-Prüfung überall
Verhalten je Konstante
Stärke
Schwäche
switch in einer Methode
alles an einer Stelle
keine Prüfung auf Vollständigkeit
Abstrakte Methode je Konstante
Compiler erzwingt jede
viel Rumpfcode
Schnittstelle implementieren
Vorgabe im Enum möglich
zusätzlicher Typ
Funktionales Feld
sehr kompakt
Logik nur für Einzeiler geeignet
EnumSet speichert als Bitmuster, EnumMap nutzt die Position als Feldindex —
beide sind ihren allgemeinen Gegenstücken überlegen. Für Enums als Werte statt
als Schlüssel gibt es kein optimiertes Gegenstück.
Anonyme Klassen sehen zusätzlich die lokalen Variablen und Parameter ihrer
Methode — sofern diese effektiv final sind. Einen Konstruktor können sie nicht
haben, weil sie keinen Namen tragen.
public static <T extends Comparable<T>> T groesstes(Collection<T> menge)public static <T extends Benannt & Comparable<T>> String nameDesGroessten(…)
Schranken stehen immer mit extends, auch bei Schnittstellen — ein implements
gibt es an dieser Stelle nicht. Mehrere Schranken werden mit & verbunden;
eine Klasse darf dabei nur an erster Stelle stehen. Statische Methoden sehen
die Typparameter ihres Typs nicht — die gehören zur Instanz.
Eine Meldung mit capture#1 of ? heißt fast immer: Aus demselben Argument wird
gelesen und geschrieben. Dann ist ein Typparameter fällig, keine Wildcard.
Zwei Fragezeichen in einer Signatur meinen nie denselben Typ.
Weil Arrays reifizierbar und kovariant sind, Generics aber nicht, lässt sich
kein Array eines parametrisierten Typs erzeugen — die Meldung lautet knapp
„generic array creation”. Varargs sind Arrays und damit die Hintertür, durch
die Heap Pollution ohne Fehler und ohne Warnung entsteht. @SafeVarargs ist
eine Zusicherung des Autors: Der Compiler prüft sie nicht nach, und sie greift
nur bei finalen, statischen oder Konstruktor-Methoden.
Zugriffsmethoden eines Records mit get benannt — sie heißen wie die
Komponente.
Im kompakten Konstruktor this.feld zugewiesen. Das Feld ist final und
wird vom Compiler beschrieben; zuweisen lässt sich nur der Parameter.
Nur beim Hineingeben kopiert und die Zugriffsmethode die Originalliste
zurückgeben lassen. Collections.unmodifiableList ist eine Sicht, keine Kopie.
ordinal() in der Datenbank gespeichert und die Konstanten später
umsortiert — die Zahlen ändern sich stillschweigend.
Ein veränderliches Feld im Enum. Alle Aufrufer teilen sich dieselbe
Instanz; das ist globaler Zustand.
static bei einer verschachtelten Klasse vergessen. Sie hält dann einen
verborgenen Verweis auf die äußere Instanz — mit Folgen für Speicher und
Serialisierung.
default im switch über einen versiegelten Typ ergänzt, weil die IDE es
vorschlägt. Damit ist die Vollständigkeitsprüfung weg.
implements statt extends bei einer Typparameter-Schranke geschrieben.
Wildcards im Rückgabetyp verwendet und beim Aufrufer eine Umwandlung
nachgezogen, statt die Signatur zu reparieren.
Rohtypen benutzt, um einem Wildcard-Problem auszuweichen. Damit ist die
Typprüfung abgeschaltet, und der Fehler erscheint erst beim Lesen.
@SuppressWarnings("unchecked") gesetzt, ohne begründen zu können, warum
die Umwandlung sicher ist. Die Annotation beseitigt die Meldung, nicht das
Problem.
@Retention weggelassen und sich über leere Reflection-Ergebnisse
gewundert.
Wir würden gern messen, wie die Seite genutzt wird, und laden dafür den
Google Tag Manager. Ohne Ihre Zustimmung passiert das nicht — die Seite
funktioniert vollständig auch ohne. Details in der
Datenschutzerklärung.