So beschreiben Sie Java-Code mit Anmerkungen
Sie sind wahrscheinlich auf Situationen gestoßen, in denen Sie Metadaten (Daten, die andere Daten beschreiben) Klassen, Methoden und / oder anderen Anwendungselementen zuordnen müssen. Beispielsweise muss Ihr Programmierteam möglicherweise unfertige Klassen in einer großen Anwendung identifizieren. Für jede nicht abgeschlossene Klasse enthalten die Metadaten wahrscheinlich den Namen des Entwicklers, der für die Beendigung der Klasse verantwortlich ist, und das voraussichtliche Abschlussdatum der Klasse.
Vor Java 5 waren Kommentare der einzige flexible Mechanismus, den Java zum Zuordnen von Metadaten zu Anwendungselementen bieten musste. Kommentare sind jedoch eine schlechte Wahl. Da der Compiler sie ignoriert, sind zur Laufzeit keine Kommentare verfügbar. Und selbst wenn sie verfügbar wären, müsste der Text analysiert werden, um wichtige Datenelemente zu erhalten. Ohne Standardisierung der Angabe der Datenelemente können diese Datenelemente möglicherweise nicht analysiert werden.
download Code herunterladen Laden Sie den Quellcode herunter, um Beispiele in diesem Java 101-Tutorial zu erhalten. Erstellt von Jeff Friesen für.Nicht standardmäßige Annotationsmechanismen
Java bietet nicht standardmäßige Mechanismen zum Zuordnen von Metadaten zu Anwendungselementen. Mit dem transient
reservierten Wort können Sie beispielsweise Felder mit Anmerkungen versehen (Daten zuordnen), die während der Serialisierung ausgeschlossen werden sollen.
Java 5 änderte alles durch die Einführung von Anmerkungen , einem Standardmechanismus zum Zuordnen von Metadaten zu verschiedenen Anwendungselementen. Dieser Mechanismus besteht aus vier Komponenten:
- Ein
@interface
Mechanismus zum Deklarieren von Annotationstypen. - Meta-Annotationstypen, mit denen Sie die Anwendungselemente identifizieren können, für die ein Annotationstyp gilt. die Lebensdauer einer Annotation (eine Instanz eines Annotationstyps) zu identifizieren ; und mehr.
- Unterstützung für die Verarbeitung von Anmerkungen über eine Erweiterung der Java Reflection-API (wird in einem zukünftigen Artikel erläutert), mit der Sie die Laufzeitanmerkungen eines Programms ermitteln können, sowie ein allgemeines Tool für die Verarbeitung von Anmerkungen.
- Standard-Annotationstypen.
Ich werde erklären, wie diese Komponenten verwendet werden, während wir uns durch diesen Artikel arbeiten.
Annotationstypen mit @interface deklarieren
Sie können einen Anmerkungstyp deklarieren, indem Sie das @
Symbol unmittelbar nach dem interface
reservierten Wort und einer Kennung angeben. In Listing 1 wird beispielsweise ein einfacher Annotationstyp deklariert, mit dem Sie threadsicheren Code annotieren können.
Listing 1:ThreadSafe.java
public @interface ThreadSafe {}
Stellen Sie nach dem Deklarieren dieses Anmerkungstyps den Methoden, die Sie als threadsicher betrachten, Instanzen dieses Typs voran, indem Sie den Methodenkopfzeilen @
unmittelbar gefolgt vom Typnamen voranstellen . Listing 2 bietet ein einfaches Beispiel, in dem die main()
Methode mit Anmerkungen versehen ist @ThreadSafe
.
Listing 2:AnnDemo.java
(Version 1)
öffentliche Klasse AnnDemo {@ThreadSafe public static void main (String [] args) {}}
ThreadSafe
Instanzen liefern keine anderen Metadaten als den Namen des Anmerkungstyps. Sie können jedoch Metadaten bereitstellen, indem Sie diesem Typ Elemente hinzufügen, wobei ein Element ein Methodenkopf ist, der im Hauptteil des Anmerkungstyps platziert wird.
Elemente haben nicht nur keine Codekörper, sondern unterliegen auch den folgenden Einschränkungen:
- Der Methodenheader kann keine Parameter deklarieren.
- Der Methodenheader kann keine Throws-Klausel bereitstellen.
- Der Rückgabetyp der Methode Header muss einen primitiven Typ (zB sein
int
)java.lang.String
,java.lang.Class
eine ENUM, ein Annotation - Typ, oder eine Anordnung von einem dieses Typ. Für den Rückgabetyp kann kein anderer Typ angegeben werden.
Als weiteres Beispiel enthält Listing 3 einen ToDo
Anmerkungstyp mit drei Elementen, die einen bestimmten Codierungsjob identifizieren, das Datum angeben, an dem der Job beendet werden soll, und den Codierer benennen, der für die Ausführung des Jobs verantwortlich ist.
Listing 3:ToDo.java
(Version 1)
public @interface ToDo {int id (); String finishDate (); String coder () default "n / a"; }}
Beachten Sie, dass jedes Element keine Parameter oder Throws-Klausel deklariert, einen zulässigen Rückgabetyp ( int
oder String
) hat und mit einem Semikolon endet. Das letzte Element zeigt auch, dass ein Standardrückgabewert angegeben werden kann. Dieser Wert wird zurückgegeben, wenn eine Anmerkung dem Element keinen Wert zuweist.
Listing 4 wird verwendet ToDo
, um eine unvollendete Klassenmethode mit Anmerkungen zu versehen.
Listing 4:AnnDemo.java
(Version 2)
öffentliche Klasse AnnDemo {public static void main (String [] args) {String [] Städte = {"New York", "Melbourne", "Peking", "Moskau", "Paris", "London"}; sort (Städte); } @ToDo (id = 1000, finishDate = "10/10/2019", coder = "John Doe") statische Leersortierung (Object [] -Objekte) {}}
Listing 4 weist jedem Element ein Metadatenelement zu. zum Beispiel 1000
ist zugeordnet id
. Anders als coder
die id
und finishDate
müssen Elemente festgelegt werden; Andernfalls meldet der Compiler einen Fehler. Wenn coder
kein Wert zugewiesen ist, wird der Standardwert angenommen "n/a"
.
Java bietet ein spezielles String value()
Element, mit dem eine durch Kommas getrennte Liste von Metadatenelementen zurückgegeben werden kann. Listing 5 zeigt dieses Element in einer überarbeiteten Version von ToDo
.
Listing 5:ToDo.java
(Version 2)
public @interface ToDo {String value (); }}
Wenn value()
es sich um das einzige Element eines Anmerkungstyps handelt, müssen Sie value
den =
Zuweisungsoperator nicht angeben , wenn Sie diesem Element eine Zeichenfolge zuweisen. Listing 6 zeigt beide Ansätze.
Listing 6:AnnDemo.java
(Version 3)
öffentliche Klasse AnnDemo {public static void main (String [] args) {String [] Städte = {"New York", "Melbourne", "Peking", "Moskau", "Paris", "London"}; sort (Städte); } @ToDo (value = "1000,10 / 10/2019, John Doe") statische Leersortierung (Object [] -Objekte) {} @ToDo ("1000,10 / 10/2019, John Doe") statische boolesche Suche ( Objekt [] Objekte, Objektschlüssel) {return false; }}
Verwendung von Meta-Annotation-Typen - das Problem der Flexibilität
Sie können Typen (z. B. Klassen), Methoden, lokale Variablen und mehr mit Anmerkungen versehen. Diese Flexibilität kann jedoch problematisch sein. Beispielsweise möchten Sie sich möglicherweise nur ToDo
auf Methoden beschränken , aber nichts hindert sie daran, andere Anwendungselemente mit Anmerkungen zu versehen, wie in Listing 7 gezeigt.
Listing 7:AnnDemo.java
(Version 4)
@ToDo ("1000,10 / 10/2019, John Doe") öffentliche Klasse AnnDemo {public static void main (String [] args) {@ToDo (value = "1000,10 / 10/2019, John Doe") String [] Städte = {"New York", "Melbourne", "Peking", "Moskau", "Paris", "London"}; sort (Städte); } @ToDo (value = "1000,10 / 10/2019, John Doe") statische Leersortierung (Object [] -Objekte) {} @ToDo ("1000,10 / 10/2019, John Doe") statische boolesche Suche ( Objekt [] Objekte, Objektschlüssel) {return false; }}
In Listing 7 ToDo
wird auch die AnnDemo
Klasse und die cities
lokale Variable mit Anmerkungen versehen . Das Vorhandensein dieser fehlerhaften Anmerkungen kann jemanden verwirren, der Ihren Code oder sogar Ihre eigenen Tools zur Verarbeitung von Anmerkungen überprüft. Für die Zeiten, in denen Sie die Flexibilität eines Annotationstyps einschränken müssen, bietet Java den Target
Annotationstyp in seinem java.lang.annotation
Paket an.
Target
ist ein Meta-Annotationstyp - ein Annotationstyp, dessen Annotationen Annotationstypen annotieren, im Gegensatz zu einem Nicht-Meta-Annotationstyp, dessen Annotationen Anwendungselemente wie Klassen und Methoden annotieren. Es identifiziert die Arten von Anwendungselementen, auf die ein Anmerkungstyp anwendbar ist. Diese Elemente werden durch Target
das ElementValue[] value()
Element identifiziert .
java.lang.annotation.ElementType
is an enum whose constants describe application elements. For example, CONSTRUCTOR
applies to constructors and PARAMETER
applies to parameters. Listing 8 refactors Listing 5’s ToDo
annotation type to restrict it to methods only.
Listing 8:ToDo.java
(version 3)
import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface ToDo { String value(); }
Given the refactored ToDo
annotation type, an attempt to compile Listing 7 now results in the following error message:
AnnDemo.java:1: error: annotation type not applicable to this kind of declaration @ToDo("1000,10/10/2019,John Doe") ^ AnnDemo.java:6: error: annotation type not applicable to this kind of declaration @ToDo(value="1000,10/10/2019,John Doe") ^ 2 errors
Additional meta-annotation types
Java 5 introduced three additional meta-annotation types, which are found in the java.lang.annotation
package:
Retention
indicates how long annotations with the annotated type are to be retained. This type’s associatedjava.lang.annotation.RetentionPolicy
enum declares constantsCLASS
(compiler records annotations in class file; virtual machine doesn’t retain them to save memory — default policy),RUNTIME
(compiler records annotations in class file; virtual machine retains them), andSOURCE
(compiler discards annotations).Documented
indicates that instances ofDocumented
-annotated annotations are to be documented byjavadoc
and similar tools.Inherited
indicates that an annotation type is automatically inherited.
Java 8 introduced the java.lang.annotation.Repeatable
meta-annotation type. Repeatable
is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. In other words, you can apply multiple annotations from the same repeatable annotation type to an application element, as demonstrated here:
@ToDo(value = "1000,10/10/2019,John Doe") @ToDo(value = "1001,10/10/2019,Kate Doe") static void sort(Object[] objects) { }
This example assumes that ToDo
has been annotated with the Repeatable
annotation type.
Processing annotations
Annotations are meant to be processed; otherwise, there’s no point in having them. Java 5 extended the Reflection API to help you create your own annotation processing tools. For example, Class
declares an Annotation[] getAnnotations()
method that returns an array of java.lang.Annotation
instances describing annotations present on the element described by the Class
object.
Listing 9 presents a simple application that loads a class file, interrogates its methods for ToDo
annotations, and outputs the components of each found annotation.
Listing 9:AnnProcDemo.java
import java.lang.reflect.Method; public class AnnProcDemo { public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("usage: java AnnProcDemo classfile"); return; } Method[] methods = Class.forName(args[0]).getMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(ToDo.class)) { ToDo todo = methods[i].getAnnotation(ToDo.class); String[] components = todo.value().split(","); System.out.printf("ID = %s%n", components[0]); System.out.printf("Finish date = %s%n", components[1]); System.out.printf("Coder = %s%n%n", components[2]); } } } }
After verifying that exactly one command-line argument (identifying a class file) has been specified, main()
loads the class file via Class.forName()
, invokes getMethods()
to return an array of java.lang.reflect.Method
objects identifying all public
methods in the class file, and processes these methods.
Method processing begins by invoking Method
’s boolean isAnnotationPresent(Class annotationClass)
method to determine if the annotation described by ToDo.class
is present on the method. If so, Method
’s T getAnnotation(Class annotationClass)
method is called to obtain the annotation.
The ToDo
annotations that are processed are those whose types declare a single String value()
element (see Listing 5). Because this element’s string-based metadata is comma-separated, it needs to be split into an array of component values. Each of the three component values is then accessed and output.
Compile this source code (javac AnnProcDemo.java
). Before you can run the application, you’ll need a suitable class file with @ToDo
annotations on its public
methods. For example, you could modify Listing 6’s AnnDemo
source code to include public
in its sort()
and search()
method headers. You’ll also need Listing 10’s ToDo
annotation type, which requires the RUNTIME
retention policy.
Listing 10:ToDo.java
(version 4)
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ToDo { String value(); }
Compile the modified AnnDemo.java
and Listing 10, and execute the following command to process AnnDemo
’s ToDo
annotations:
java AnnProcDemo AnnDemo
If all goes well, you should observe the following output:
ID = 1000 Finish date = 10/10/2019 Coder = John Doe ID = 1000 Finish date = 10/10/2019 Coder = John Doe
Processing annotations with apt and the Java compiler
In Java 5 wurde ein apt
Tool zur verallgemeinerten Verarbeitung von Anmerkungen eingeführt . Java 6 migrierte apt
die Funktionalität in sein javac
Compiler-Tool und Java 7 war veraltet apt
, was anschließend entfernt wurde (beginnend mit Java 8).
Standard-Annotationstypen
Zusammen mit Target
, Retention
, Documented
, und Inherited
, Java 5 eingeführt java.lang.Deprecated
, java.lang.Override
und java.lang.SuppressWarnings
. Diese drei Annotationstypen sind nur für die Verwendung in einem Compilerkontext vorgesehen, weshalb ihre Aufbewahrungsrichtlinien auf festgelegt sind SOURCE
.