Start / Cheat Sheets

Cheat Sheet

Java: Records, Sealed Types, Generics — Cheat Sheet

Stand: · Java SE Fortgeschrittene Sprachfeatures

JavaGenericsRecordsAnnotationen

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 wirdEigenschaft
Ein Feld je Komponenteprivate und final
Der kanonische KonstruktorParameter = Komponenten in gleicher Reihenfolge
Je eine Zugriffsmethodeheißt wie die Komponente, ohne get-Präfix
equals und hashCodevergleichen bzw. verrechnen alle Komponenten
toStringlistet alle Komponenten mit Werten auf
Regel für KonstruktorenFolge im Alltag
Nur der kanonische Konstruktor beschreibt FelderJede Prüfung gehört genau dorthin
Zusatzkonstruktoren beginnen mit this(...)Vorbelegungen laufen immer durch die Prüfung
Sichtbarkeit nicht enger als der Record selbstPublic Record heißt public Konstruktor
Kompaktform ohne ParameterlisteZuweisungen 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.

Modul: Records

Sealed: Hierarchien abschließen

MittelErlaubtAussage
finalkeine AbleitungHier ist Schluss
offenjede AbleitungErweiterung erwünscht
Konstruktor paketprivatAbleitung im PaketNebenwirkung, kein Entwurf
sealed mit permitsgenannte TypenDie Menge ist vollständig
Regel, die der Compiler erzwingtGrund
Jeder Untertyp ist final, sealed oder non-sealedSonst ließe sich die Sperre umgehen
Untertypen im selben Modul bzw. PaketDie Menge muss übersetzbar bekannt sein
permits entfällt bei einer QuelldateiDer Compiler sieht die Untertypen ohnehin
Records sind implizit finalSie können nicht sealed sein, aber implementieren
public sealed interface Position
        permits Artikelposition, Frachtzuschlag { }

non-sealed öffnet einen Zweig ausdrücklich wieder — und gibt für ihn die Vollständigkeitsprüfung auf.

Modul: Sealed Classes

Datenmodell: UND und ODER

BausteinBeziehungAusdrucksmittel
ProdukttypUNDRecord mit Komponenten
SummentypODERVersiegelte Klasse oder Schnittstelle
SummentypODEREnum bei festen Einzelwerten
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.

FrageDaten und Verhalten getrenntVerhalten in der Variante
Neue Auswertungein neuer switchMethode in jeder Variante
Neue Variantejeder switch meldet sicheine neue Klasse genügt
Wo steht die Logikan einer Stelle gebündeltüber die Varianten verteilt

Modul: Datenmodellierung

Enums: Bordmittel und Muster

MitgliedArtLiefert
values()statischFeld aller Konstanten
valueOf(String)statischKonstante zum exakten Namen
name()InstanzNamen der Konstante als String
ordinal()InstanzPosition in der Deklaration
compareToInstanzReihenfolge der Deklaration

valueOf findet nur über den exakten Namen — für gebundene Kürzel oder Zahlencodes braucht es eine eigene statische Suchmethode.

Rückgabe bei unbekanntem WertPasst, wennKostet
Ausnahme werfenDaten müssen gültig seinAufrufer braucht Fehlerbehandlung
Optional liefernFehlen ist normalAufrufer muss auspacken
Vorgabewert liefernEs gibt einen sinnvollenFehler bleiben unsichtbar
null liefernselten sinnvollNull-Prüfung überall
Verhalten je KonstanteStärkeSchwäche
switch in einer Methodealles an einer Stellekeine Prüfung auf Vollständigkeit
Abstrakte Methode je KonstanteCompiler erzwingt jedeviel Rumpfcode
Schnittstelle implementierenVorgabe im Enum möglichzusätzlicher Typ
Funktionales Feldsehr kompaktLogik 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.

Modul: Enum-Entwurfsmuster

Verschachtelte und innere Typen

Verschachtelter TypStatisch?Bemerkung
Klasse mit staticjaeigenständig, kein Verweis nach außen
Klasse ohne staticneininnere Klasse, an eine Instanz gebunden
Schnittstelleimmernichtstatisch gibt es nicht
Enumimmernichtstatisch gibt es nicht
Recordimmernichtstatisch gibt es nicht
MerkmalStatisch verschachteltInnere Klasse
Verweis nach außenkeinerverborgen vorhanden
Erzeugennew Aussen.Innen()aussen.new Innen()
Zugriff auf Instanzfeldernur über Parameterunmittelbar
Serialisierungbetrifft nur den Typzieht die äußere Instanz mit

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.

Modul: Innere Typen

Generics: Begriffe und Schranken

BegriffWertebeneTypebene
DeklarationParameter in der SignaturTypparameter in spitzen Klammern
AufrufArgument beim AufrufTypargument bei der Verwendung
Ergebnisinitialisierter Parameterparametrisierter Typ
TypTypparameter erlaubt?
Klasse, Schnittstelle, Recordja
Enumnein
Ausnahmeklassen unterhalb von Throwablenein
Anonyme innere Klassenein, kann aber generische Typen umsetzen
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.

Modul: Generics

Wildcards und PECS

SchreibweiseBedeutungBeispiel aus List
?ein beliebiger TypcontainsAll(Collection<?>)
? extends TT oder ein UntertypaddAll(Collection<? extends E>)
? super TT oder ein Obertypsort(Comparator<? super E>)
Rolle des ParametersWildcardErlaubt im Rumpf
Produzent, Methode liest? extends TLesen als T
Konsument, Methode schreibt? super TEinfügen von T
Beides zugleichkeine WildcardTypparameter verwenden
FrageList<Object>List<?>
Inhaltbeliebige Objekte, gemischtObjekte eines unbekannten Typs
Element hinzufügenerlaubtnur null
Argument List<String>wird abgelehntwird angenommen
EinschränkungAusweg
Eine Wildcard hat höchstens eine SchrankeTypparameter mit mehreren Schranken
Obere und untere Schranke zugleich geht nichtSignatur neu schneiden
Wildcards im Rückgabetyp verwerfen InformationTypparameter verwenden
PECS gilt nur für ParameterRückgabetyp getrennt entscheiden

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.

Modul: Wildcards

Type Erasure und ihre Folgen

Nicht möglichWeil zur Laufzeit
new T()der Typ T nicht bekannt ist
x instanceof List<Zugang>die Typargumente entfernt sind
List<String>.classes kein solches Klassenobjekt gibt
Überladen mit gleicher Löschungbeide Signaturen identisch wären
Geprüfte Umwandlung auf List<Zugang>die Prüfung nicht durchführbar ist
FormBedeutungIn Java
Invarianzkeine Beziehung zwischen den ListenRegelfall bei Generics
KovarianzUntertyp bleibt Untertypüber ? extends T
KontravarianzObertyp darf einspringenüber ? super T
MerkmalArrayParametrisierter Typ
Elementtyp zur Laufzeitbekanntentfernt
Varianzkovariantinvariant
Prüfungzur Laufzeitbeim Übersetzen
FehlerbildArrayStoreExceptionÜbersetzungsfehler

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.

Modul: Generics im Detail

Annotationen

Standard-AnnotationWirkung
@OverrideFehler, wenn nichts überschrieben wird
@DeprecatedWarnung bei jeder Verwendung
@SuppressWarningsUnterdrückt benannte Warnungen
@SafeVarargsZusicherung bei generischen Varargs
@FunctionalInterfaceFehler, wenn mehr als eine abstrakte Methode
Meta-AnnotationLegt fest
@TargetAn welchen Stellen die Annotation stehen darf
@RetentionBis wann sie aufbewahrt wird
@DocumentedOb sie im Javadoc erscheint
@InheritedOb Untertypen sie erben (nur Klassen, nicht Schnittstellen)
@RepeatableOb sie mehrfach gesetzt werden darf
RetentionVorhanden bisPassend für
SOURCEEnde der ÜbersetzungAnnotationsprozessoren
CLASSin der KlassendateiWerkzeuge, die Klassendateien lesen
RUNTIMEzur Laufzeit lesbarAuswertung über Reflection

CLASS ist die Vorgabe. Wer @Retention weglässt und zur Laufzeit sucht, findet nichts — ohne Fehler, ohne Warnung.

Attributtyp erlaubtNicht erlaubt
Primitive Typen und StringHüllklassen wie Integer
Class oder Class mit Wildcardbeliebige Objekttypen
Enum-Typengenerische Typen wie List<String>
Andere Annotationstypennull als Vorgabewert
Arrays der genannten TypenWerte aus Konfigurationsdateien

Heißt ein Attribut value und wird nur dieses gesetzt, entfällt bei der Anwendung das value =. Alle Vorgabewerte müssen beim Übersetzen feststehen.

Modul: Annotationen

Typische Fallen

  • 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.

Zum Seminar Java SE Fortgeschrittene Sprachfeatures