Eingebaute Java-Annotationen
Die wichtigsten eingebauten Java-Annotationen — @Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface.
Die Standardbibliothek enthält eine kleine Auswahl von Annotationen in java.lang, die der Compiler besonders behandelt. Keine von ihnen fügt zur Laufzeit Verhalten hinzu; sie sind alle Hinweise an javac (oder, im Fall von @Deprecated, an die gesamte Toolchain). Zu wissen, was jede einzelne verspricht — und was sie nicht tut — ist der praktische Kern der täglichen Arbeit mit Annotationen.
Die fünf, denen man am häufigsten begegnet:
@Override— „diese Methode überschreibt eine Methode eines Supertyps."@Deprecated— „nicht verwenden; diese API wird entfernt."@SuppressWarnings— „diese bestimmten Warnungen in diesem Gültigkeitsbereich unterdrücken."@SafeVarargs— „meine Varargs-Methode verunreinigt den Heap nicht."@FunctionalInterface— „dieses Interface hat genau eine abstrakte Methode."
@Override
Das Setzen von @Override auf eine Methode teilt dem Compiler mit, dass die Methode beabsichtigt, eine Superklassenmethode zu überschreiben oder eine Interfacemethode zu implementieren. Falls sie tatsächlich nichts überschreibt (weil der Name falsch geschrieben ist, die Signatur falsch ist oder die übergeordnete Klasse die Methode entfernt hat), bricht javac den Build ab.
class Animal {
public String speak() { return "?"; }
}
class Dog extends Animal {
@Override
public String speak() { return "woof"; } // OK
@Override
public String spaek() { return "oops"; } // compile error: nothing to override
}Dies fängt die Art von Fehler ab, die zur Laufzeit sehr schwer zu finden ist: eine überschreibende Methode, deren Signatur von der der Superklasse abweicht. Schreiben Sie @Override immer auf Methoden, die Sie überschreiben möchten. Die Annotation hat SOURCE-Retention und ist daher nach Abschluss der Kompilierung verschwunden.
@Deprecated
@Deprecated kennzeichnet etwas — eine Klasse, Methode, ein Feld oder einen Konstruktor — als nicht empfohlen. Der Compiler warnt an jeder Verwendungsstelle. Seit Java 9 nimmt die Annotation zwei Elemente entgegen:
@Deprecated(since = "1.4", forRemoval = true)
public void oldApi() { ... }sincedokumentiert, wann die Deprecation begann.forRemoval = trueist ein stärkeres Signal: Eine zukünftige Version plant, die API zu löschen. Der Compiler gibt eine Entfernungswarnung aus (in der Regel lauter als eine einfache Deprecation-Warnung) und das Javadoc-Tool kennzeichnet sie anders.
Im Gegensatz zu @Override hat @Deprecated RUNTIME-Retention — Bytecode-Werkzeuge, IDEs und Reflection können sie alle sehen. Das begleitende Javadoc-Tag @deprecated (Kleinschreibung, in einem Dokumentationskommentar) trägt die Erklärung; die Annotation aktiviert das Tooling.
@SuppressWarnings
Wenn der Compiler fast immer recht hat, aber in diesem Fall nicht, unterdrückt @SuppressWarnings eine Kategorie von Warnungen innerhalb des annotierten Elements:
@SuppressWarnings("unchecked")
List<String> strings = (List<String>) raw; // raw cast intentional
@SuppressWarnings({"unchecked", "rawtypes"})
public void uglyButNeeded() { ... }Das String-Element benennt eine Warnkategorie; die gebräuchlichsten sind unchecked, rawtypes, deprecation, serial, unused, removal. Compiler können weitere akzeptieren. Zwei Regeln, um dies sauber zu halten:
- Den kleinstmöglichen Gültigkeitsbereich annotieren. Auf einer lokalen Variablen oder einer einzelnen Methode unterdrücken, niemals auf einer Klasse, es sei denn, jede Zeile benötigt es wirklich.
- Mit einem Kommentar kombinieren, der erklärt warum. Ein bloßes
@SuppressWarnings("unchecked")neben einem Cast lässt den nächsten Leser rätseln, ob der Cast tatsächlich sicher ist.
@SafeVarargs
Eine Varargs-Methode, deren Parametertyp einen Typparameter enthält, hat ein subtiles Problem: An der Aufrufstelle muss der Compiler möglicherweise ein Array eines generischen Typs erstellen, was unsicher ist. Der Compiler warnt davor mit der Meldung „possible heap pollution". Wenn der Autor verifiziert hat, dass der Methodenrumpf das Array nicht nach außen gibt oder falsche Typen hineinschreibt, unterdrückt @SafeVarargs die Warnung:
@SafeVarargs
public final <T> List<T> listOf(T... items) {
return java.util.List.of(items); // only reads the items, never stores other types
}Regeln:
- Nur zulässig auf Methoden, die nicht überschrieben werden können —
static,finaloderprivate, sowie record-Konstruktoren. - Die Annotation ist ein Versprechen. Wenn der Methodenrumpf tatsächlich mit einem falschen Typ in das Varargs-Array schreibt, kann der Cast an der Aufrufstelle später mit einer verwirrenden
ClassCastExceptionfehlschlagen.
Die Annotation hat SOURCE-Retention.
@FunctionalInterface
Ein funktionales Interface ist eines mit genau einer abstrakten Methode — die Form, die Runnable, Callable, Comparator und Function alle gemeinsam haben. Lambda-Ausdrücke und Methodenreferenzen zielen auf funktionale Interfaces ab. Die Annotation macht die Absicht explizit und fordert den Compiler auf, die Regel der einzelnen abstrakten Methode durchzusetzen:
@FunctionalInterface
public interface StringMapper {
String map(String input); // the single abstract method
default StringMapper andThen(StringMapper next) { // default methods are allowed
return s -> next.map(map(s));
}
}Wenn später eine zweite abstrakte Methode hinzugefügt wird, schlägt der Build sofort fehl. Ohne die Annotation würde das Interface stillschweigend aufhören, als Lambda-Ziel verwendbar zu sein — was ein Benutzer erst an der Aufrufstelle bemerken würde.
Wie @Override hat es SOURCE-Retention.
Ein praktisches Beispiel: Die Compiler-Durchsetzung beobachten
Dieses Programm demonstriert die vier erzwungenen Annotationen und zeigt, was die Laufzeitumgebung danach sehen kann. Die interessanten Punkte: die @SafeVarargs-Methode kompiliert tatsächlich ohne Warnung, wo die nicht-annotierte eine Warnung ausgibt; das @FunctionalInterface wird über die SAM-Zählregeln reflektiert; die @Deprecated-Elementwerte sind zur Laufzeit abrufbar.
Was aus dem Programmablauf mitzunehmen ist:
@FunctionalInterfacehat seine Aufgabe zur Compile-Zeit erfüllt, indem es garantiert, dassStringMapperein SAM-Typ ist:String::toUpperCaseund der Lambda-Ausdrucks -> s + \"!\"haben sich beide sauber daran gebunden. Wenn jemand dem Interface eine zweite abstrakte Methode hinzufügte, würde die Kompilierung fehlschlagen und diese Ausdrücke würden aufhören aufzulösen.- Die
@Override-Zeile war die Buchführung, die sicherstellte, dassChild.describe()tatsächlichParent.describe()überschreibt. Der polymorphe Aufruf, der bei\"child\"landet, bestätigt dies; wenn die Signatur abgewichen wäre (anderer Name, anderer Rückgabetyp), hätte der Build fehlgeschlagen, anstatt falsches Laufzeitverhalten zu erzeugen. @Deprecatedist die einzige Annotation hier, die die Kompilierung überlebt. Reflection hat erfolgreichsince=1.4undforRemoval=trueaus der Klassendatei ausgelesen. Die Methode selbst wurde noch ausgeführt —@Deprecatedwarnt, deaktiviert aber nicht.@SafeVarargshat die Compile-Zeit-Warnung „possible heap pollution" entfernt, während der typsichere Aufruf erhalten blieb. Zu beachten: Die Methode iststatic, erfüllt also die Regel „kann nicht überschrieben werden". Das Entfernen der Annotation würde kompilieren, aber währendjavacschreien; das Hinzufügen auf einer nicht-statischen, nicht-finalen, nicht-privaten Methode wäre ein Compilerfehler.@SuppressWarningshinterließ keine Laufzeitspur — das ausgegebene Annotations-Array fürparseOrZeroist leer. Das ist der eigentliche Zweck derSOURCE-Retention: Die Annotation tut ihre Arbeit während der Kompilierung und verschwindet dann, sodass die Klassendatei unbelastet bleibt.
Weitere eingebaute Annotationen, die es wert sind, bekannt zu sein
Eine Handvoll weniger verbreiteter Annotationen aus der Standardbibliothek, kurz erläutert:
@SuppressWarnings("preview")— für Code, der Vorschau-Sprachfunktionen verwendet (Java 14+).@Native(java.lang.annotation.Native) — kennzeichnet eine Konstante, die möglicherweise aus nativem Code referenziert wird; wird von Werkzeugen verwendet, die JNI-Header generieren.@Generated(javax.annotation.processing.Generated, Java 9+) — von Code-Generatoren auf von ihnen emittierten Dateien hinzugefügt.@Documented,@Retention,@Target,@Inherited,@Repeatable— das sind Meta-Annotationen; im nächsten Kapitel behandelt.
Die ersten drei werden selten von Hand geschrieben. Die Meta-Annotationen sind das Tor zum Schreiben eigener Annotationstypen, und Reflection ist die Methode, mit der Laufzeit-beibehaltene Annotationen wie @Deprecated zurückgelesen werden — wie das obige Beispiel zeigt.