Annotations

https://oer-informatik.de/java-annotations

tl/dr; (ca. 8 min Lesezeit): Java Annotationen - Welche geläufigen gibt es? Wozu werden sie genutzt? Wer kann sie auslesen? Welche Eigenschaften können sie haben? Wie kann ich eigene Erstellen? Was wirt mit Retention und Target festgelegt? Was ist die Reflection API und was hat sie mit Annotations zu tun?

Annotations dienen seit Java 5 dazu, Metadata über den Code bereitzustellen. In der Oracle Java Documentation werden drei Einsatzgebiete genannt:

  • Informationen für den Compiler (um Fehler zu finden und Warnungen zu unterdrücken)

  • Auswertung beim Compilieren und beim Deployment (Zur Code oder Config-Erzeugung durch Tools)

  • Auswertung zur Laufzeit

Einfache Annotationen

Annotations können vor Attributen, Methoden und Klassen notiert werden. Sie werden immer durch ein führendes @ gekennzeichnet. Die erste Annotation, die man häufig kennenlernt, ist @Override :

Der Compiler prüft, ob die so markierte Methode tatsächlich eine geerbte Methode überschreibt. Ist dies nicht der Fall wird dies vom Compiler beanstandet.

Mit Java 8 und den Lambda-Expressions wurde die @FunctionalInterface- Annotation eingefügt und in diesem Zusammenhang viel genutzt.

Geläufig ist beispielsweise auch die @Deprecated- Annotation, mit der Klassen, Methoden oder Attribute markiert werden, die veraltet sind und nicht mehr genutzt werden sollten.

Der Compiler gibt eine Warnung aus, sofern eine als @Deprecated markierte Komponente genutzt wird.

Weitere Annotationen, die in java.lang definiert sind, sind @SafeVarargs und @SuppressWarnings.

Eigene Annotationen

Um eigene Annotationen zu implementieren müssen 3 Meta-Annotationen genutzt werden:

  • @interface kennzeichnet die Annotation als solche (kleingeschrieben!)

  • @Retention legt fest, wer die Annotation nutzen kann:

    • nur der Quelltext (für Compiler & JVM ist Annotation nicht sichtbar): RetentionPolicy.SOURCE

    • Compiler (Quelltext, aber JVM nicht): RetentionPolicy.CLASS

    • JVM: RetentionPolicy.RUNTIME

  • @Target legt fest, welche Elemente annotiert werden können. Mögliche Werte sind: ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE

Wie sieht die Definition einer eigenen Annotation aus?

Genutzt werden kann diese Annotation an Attributen (ElementType.FIELD) und sie ist zur Laufzeit sichtbar (RetentionPolicy.RUNTIME).

Die beiden an die Annotation zu übergebenden Parameter werden als Key-Value-Liste angegeben, wobei der optionale Parameter weggelassen werden kann:

Bei Annotations, bei denen lediglich ein Parameter übergeben wird, kann auch der Key weggelassen werden (siehe @SuppressWarnings("unchecked") unten).

Über weitere Meta-Annotationen kann festgelegt werden, ob diese Annotation bei einem Element mehrmals vorkommen darf (@Repeatable), ob die Annotation in JavaDoc genannt werden muss (@Documented) und die Annotation von Superklassen an Subklassen weitergegeben wird (@Inherited).

Annotations mit dem ReflectionAPI auswerten

Um zu Veranschaulichen, wie wir Annotations nutzen können, erstellen wir zunächst eine Klasse, die wir auf Annotations prüfen wollen:

Die Klasse, Methoden und Attribute wurden jeweils mit unterschiedlichsten Annotations versehen. Mit einer weiteren Klasse wollen wir über das ReflectionAPI die Annotationen dieser Klasse nutzen:

Wir nutzen die Klasse Class, um in der Methode findAnnotations Zugriff auf die Meta-Informationen einer Klasse per Reflection-API zu erhalten:

  • Bei toString() wird weder @Override noch @SuppressWarnings angezeigt

Wir nutzen getDeclaredMethods() und getDeclaredFields() um Methoden- und Feldobjekte zu erhalten, die wir dann weiter untersuchen können. Class, Method und Field implementieren das AnnotatedElement-Interface, daher können wir das Klassenobjekt und alle Attribut- und Methodenobjekte, über die die Schleifen iterieren, an unsere Methode parseAnnotatedElement weiterreichen.

Class, Method und Field implementieren wie alle die Methode getAnnotations von AnnotatedElement, die uns zu jedem Objekt die Annotaionsobjekte zurückliefert. Durch alle Annotations wiederum iteriert die Methode parseAnnotatedElement und gibt den Rückgabewert der toString()-Methode aus. Um Beispielhaft zu zeigen, wie man konkret mit Annotationen und deren Parametern operieren kann prüft die Methode, ob es sich um den selbstdefinierten Typ MyAnnotation handelt, castet das Objekt zu diesem Typ und verarbeitet die Parameter (über die bereitstellenden Methoden).

Die Ausgabe sieht wie folgt aus:

Gefundene Annotationen von Klasse (class de.csbme.annotationexample.InspectedClass)
 - @java.lang.Deprecated(forRemoval=false, since="")
Gefundene Annotationen von Method (public java.lang.String de.csbme.annotationexample.InspectedClass.toString())
 - @de.csbme.annotationexample.MyAnnotation(parameterEins="Beispielcode", parameterZwei="Hose")
Gefundene Annotationen von Method (public java.lang.String de.csbme.annotationexample.InspectedClass.dontUse())
Gefundene Annotationen von Field (private java.lang.String de.csbme.annotationexample.InspectedClass.anotherField)
 - @de.csbme.annotationexample.MyAnnotation(parameterEins="dieserWert", parameterZwei="Jacke")
Gefundene Annotationen von Field (private java.lang.String de.csbme.annotationexample.InspectedClass.myField)
 - @java.lang.Deprecated(forRemoval=false, since="")
 - @de.csbme.annotationexample.MyAnnotation(parameterEins="Beispielcode", parameterZwei="Hose")

Moment? Da fehlen doch Annotations? Warum wird weder @Override noch @SuppressWarnings ausgegeben? Des Rätsels Lösung liegt in der Deklaration der Annotations:

Die Annotation @SuppressWarnings kann zwar mit zahlreichen Targets verknüpft werden, ist aber per Retention nur dem Ziel Source zugewiesen. Für Compiler und JVM bleibt sie unsichtbar. Ebenso @Override:

@Deprecated hingegen ist Runtime zugewiesen - somit auch zur Laufzeit noch verfügbar:

Quellen und offene Ressourcen (OER)

Die Ursprungstexte (als Markdown), Grafiken und zugrunde liegende Diagrammquelltexte finden sich (soweit möglich in weiterbearbeitbarer Form) in folgendem git-Repository:

https://gitlab.com/oer-informatik/java-advanced/annotation.

Sofern nicht explizit anderweitig angegeben sind sie zur Nutzung als Open Education Resource (OER) unter Namensnennung (H. Stein, oer-informatik.de) freigegeben gemäß der Creative Commons Namensnennung 4.0 International Lizenz (CC BY 4.0).

Creative Commons Lizenzvertrag

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: