Annotations
Kurz: Metadaten, die mit @Name vor eine Klasse, Methode oder Variable geschrieben werden — beeinflussen nicht direkt die Programmlogik, sondern liefern Zusatzinformation für Compiler, Tools oder Frameworks.
Genauer: Bekannte eingebaute Annotationen: @Override (lässt den Compiler prüfen, dass wirklich eine geerbte Methode überschrieben wird — Tippfehler im Methodennamen fallen so sofort auf) und @Deprecated (markiert veraltete Elemente mit Compiler-Warnung bei Nutzung). Frameworks wie Spring nutzen eigene Annotationen extensiv, um Konfiguration statt in separaten Dateien direkt im Code zu hinterlegen.
Im Detail
Annotationen selbst können auch eigene, benutzerdefinierte sein — dafür wird ein eigener Annotationstyp mit @interface definiert, meist zusammen mit einer @Retention-Meta-Annotation, die festlegt, wie lange die Annotation “sichtbar” bleibt:
@Retention(RetentionPolicy.RUNTIME) // auch zur Laufzeit per Reflection abrufbar
@Target(ElementType.METHOD) // nur auf Methoden anwendbar
@interface Testbar {
String beschreibung() default "";
}
class Rechner {
@Testbar(beschreibung = "prüft Addition")
int addiere(int a, int b) { return a + b; }
}RetentionPolicy.SOURCE (nur für den Compiler relevant, z. B. @Override), CLASS (landet in der .class-Datei, aber nicht mehr zur Laufzeit abrufbar) und RUNTIME (per Reflection zur Laufzeit auslesbar) sind die drei Stufen. Frameworks wie Spring oder JUnit nutzen genau diesen RUNTIME-Mechanismus, um z. B. @Test-markierte Methoden automatisch zu finden und auszuführen, ohne dass man sie manuell registrieren muss — die Annotation selbst tut nichts, das auslesende Framework entscheidet, was sie bedeutet.
Reflection als Gegenstück
Damit eine RUNTIME-Annotation überhaupt etwas bewirkt, muss irgendein Code sie zur Laufzeit per Reflection auslesen:
Method m = Rechner.class.getMethod("addiere", int.class, int.class);
if (m.isAnnotationPresent(Testbar.class)) {
Testbar t = m.getAnnotation(Testbar.class);
System.out.println("Test: " + t.beschreibung());
}Genau dieses Muster — Methoden nach einer Annotation durchsuchen und dann per Reflection aufrufen — steckt hinter praktisch jedem Test-Runner und jedem Dependency-Injection-Framework in Java.
Eingebaute Standard-Annotationen im Überblick
Neben @Override und @Deprecated gehören zu den wichtigsten eingebauten Annotationen: @SuppressWarnings("unchecked") (unterdrückt gezielt eine Compiler-Warnung, z. B. bei unvermeidbaren Generics-Type-Casts), @FunctionalInterface (lässt den Compiler prüfen, dass ein Interface wirklich genau eine abstrakte Methode hat — Voraussetzung für Lambda-Ausdrücke) und @SafeVarargs (bestätigt, dass eine Varargs-Methode mit Generics keine Heap-Pollution verursacht).
Häufiger Fallstrick
Eine Annotation OHNE passende @Retention(RUNTIME) per Reflection auslesen zu wollen, scheitert stillschweigend — getAnnotation() liefert dann einfach null zurück, ohne Fehlermeldung. Das ist eine häufige Ursache für “meine Annotation wird ignoriert”-Bugs bei selbstgeschriebenen Frameworks.
Annotationen auf verschiedenen Zielen
@Target legt fest, wo eine benutzerdefinierte Annotation überhaupt erlaubt ist — ElementType.TYPE (Klassen/Interfaces), METHOD, FIELD, PARAMETER oder CONSTRUCTOR sind die gängigsten. Mehrere Ziele lassen sich als Array angeben:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface Wichtig {}Ohne @Target darf die Annotation überall verwendet werden — meist ungewollt, da der Compiler dann nicht mehr helfen kann, Fehlanwendungen (z. B. eine eigentlich nur für Methoden gedachte Annotation auf einer Klasse) frühzeitig abzufangen.
Vergleich zu Interfaces und Markern
Vor Java 5 (das Annotationen einführte) wurden ähnliche Zwecke oft über sogenannte “Marker-Interfaces” gelöst — leere Interfaces wie Serializable, die eine Klasse nur implementiert, um eine Eigenschaft zu signalisieren, ohne echte Methoden bereitzustellen. Annotationen lösen dasselbe Problem eleganter: Sie können Parameter tragen (@Testbar(beschreibung = "...")), lassen sich mehrfach auf verschiedene Elemente derselben Klasse anwenden und verschmutzen nicht die Vererbungshierarchie der Klasse selbst. Serializable existiert aus Kompatibilitätsgründen bis heute als Marker-Interface, neue APIs setzen aber fast durchgängig auf Annotationen.