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?
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention (RetentionPolicy.RUNTIME)
@Target ({ElementType.FIELD, ElementType.METHOD})
public @interface MeineAnnotation {
public String parameterEins() default "Beispielcode";
public String parameterZwei();
}
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:
@MyAnnotation(parameterZwei = "Hose")
private String myField;
@MyAnnotation(parameterEins = "dieserWert", parameterZwei = "Jacke")
private String anotherField;
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:
@Deprecated
public class InspectedClass {
@MyAnnotation(parameterEins = "dieserWert", parameterZwei = "Jacke")
private String anotherField;
@Deprecated
@MyAnnotation(parameterZwei = "Hose")
private String myField;
@SuppressWarnings("unchecked")
public String dontUse() {return "nein!";}
@Override
@SuppressWarnings(value="unchecked")
@MyAnnotation(parameterZwei = "Hose")
public String toString() {return "ja";}
}
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:
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SimpleAnnotationExample {
public static void main(String[] args) {
findAnnotations(InspectedClass.class);
}
public static void parseAnnotatedElement(AnnotatedElement element, String elementName){ /*...*/ }
public static void findAnnotations(Class checkedClass) { /*...*/ }
}
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
public static void findAnnotations(Class checkedClass) {
parseAnnotatedElement(checkedClass, "Klasse");
for (Method method : checkedClass.getDeclaredMethods()) {
parseAnnotatedElement(method, "Method");
}
for (Field field : checkedClass.getDeclaredFields()) {
parseAnnotatedElement(field, "Field");
}
}
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.
public static void parseAnnotatedElement(AnnotatedElement element, String elementName){
Annotation[] annotations = element.getAnnotations();
System.out.println("Gefundene Annotationen von "+elementName + " ("+element.toString() + ")");
for (Annotation annotation : annotations) {
System.out.println(" - " + annotation);
if (annotation.annotationType().isAnnotationPresent(MyAnnotation.class)) {
//Casten, um auf die Attribute zugreifen zu können:
MyAnnotation specifiedAnnotation = (MyAnnotation) annotation;
System.out.println(" - parameterEins: " + specifiedAnnotation.parameterEins());
System.out.println(" - parameterZwei: " + specifiedAnnotation.parameterZwei());
}
}
}
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:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings { /* */ }
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:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated { /* */ }
Weiterführende Literatur / Links
- Explore Annotations in Java 8: https://dzone.com/articles/explore-annotations-in-java-8
- Oracle Java Documentation: https://docs.oracle.com/javase/tutorial/java/annotations/
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).