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 transientreservierten 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 @interfaceMechanismus 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 interfacereservierten 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) {}}

ThreadSafeInstanzen 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.Classeine 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 ToDoAnmerkungstyp 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 ( intoder 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 1000ist zugeordnet id. Anders als coderdie idund finishDatemüssen Elemente festgelegt werden; Andernfalls meldet der Compiler einen Fehler. Wenn coderkein 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 valueden =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 ToDoauf 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 ToDowird auch die AnnDemoKlasse und die citieslokale 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 TargetAnnotationstyp in seinem java.lang.annotationPaket an.

Targetist 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 Targetdas 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 associated java.lang.annotation.RetentionPolicy enum declares constants CLASS (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), and SOURCE (compiler discards annotations).
  • Documented indicates that instances of Documented-annotated annotations are to be documented by javadoc 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 aptTool zur verallgemeinerten Verarbeitung von Anmerkungen eingeführt . Java 6 migrierte aptdie Funktionalität in sein javacCompiler-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.Overrideund java.lang.SuppressWarnings. Diese drei Annotationstypen sind nur für die Verwendung in einem Compilerkontext vorgesehen, weshalb ihre Aufbewahrungsrichtlinien auf festgelegt sind SOURCE.

Veraltet