Come descrivere il codice Java con annotazioni

Probabilmente hai riscontrato situazioni in cui è necessario associare metadati (dati che descrivono altri dati) a classi, metodi e / o altri elementi dell'applicazione. Ad esempio, il tuo team di programmazione potrebbe dover identificare le classi non completate in un'applicazione di grandi dimensioni. Per ogni classe incompiuta, i metadati includeranno probabilmente il nome dello sviluppatore responsabile del completamento della classe e la data di completamento prevista della classe.

Prima di Java 5, i commenti erano l'unico meccanismo flessibile che Java aveva da offrire per associare i metadati agli elementi dell'applicazione. Tuttavia, i commenti sono una scelta sbagliata. Poiché il compilatore li ignora, i commenti non sono disponibili in fase di esecuzione. E anche se fossero disponibili, il testo dovrebbe essere analizzato per ottenere elementi di dati cruciali. Senza standardizzare il modo in cui gli elementi di dati sono specificati, questi elementi di dati potrebbero rivelarsi impossibili da analizzare.

download Ottieni il codice Scarica il codice sorgente per esempi in questo tutorial di Java 101. Creato da Jeff Friesen per.

Meccanismi di annotazione non standard

Java fornisce meccanismi non standard per l'associazione di metadati con elementi dell'applicazione. Ad esempio, la transientparola riservata consente di annotare (associare dati a) campi che devono essere esclusi durante la serializzazione.

Java 5 ha cambiato tutto introducendo le annotazioni , un meccanismo standard per associare metadati a vari elementi dell'applicazione. Questo meccanismo è costituito da quattro componenti:

  • Un @interfacemeccanismo per dichiarare i tipi di annotazione.
  • Tipi di meta-annotazione, che è possibile utilizzare per identificare gli elementi dell'applicazione a cui si applica un tipo di annotazione; identificare la durata di un'annotazione (un'istanza di un tipo di annotazione); e altro ancora.
  • Supporto per l'elaborazione delle annotazioni tramite un'estensione dell'API Java Reflection (che verrà discussa in un articolo futuro), che è possibile utilizzare per scoprire le annotazioni di runtime di un programma e uno strumento generalizzato per l'elaborazione delle annotazioni.
  • Tipi di annotazioni standard.

Spiegherò come utilizzare questi componenti mentre lavoriamo in questo articolo.

Dichiarazione dei tipi di annotazione con @interface

È possibile dichiarare un tipo di annotazione specificando il @simbolo immediatamente seguito dalla interfaceparola riservata e da un identificatore. Ad esempio, il listato 1 dichiara un semplice tipo di annotazione che potresti usare per annotare il codice thread-safe.

Listato 1:ThreadSafe.java

public @interface ThreadSafe {}

Dopo aver dichiarato questo tipo di annotazione, anteponi i metodi che consideri thread-safe con istanze di questo tipo anteponendo @immediatamente seguito dal nome del tipo alle intestazioni del metodo. Il Listato 2 offre un semplice esempio in cui il main()metodo è annotato @ThreadSafe.

Listato 2:AnnDemo.java (versione 1)

public class AnnDemo {@ThreadSafe public static void main (String [] args) {}}

ThreadSafele istanze non forniscono metadati diversi dal nome del tipo di annotazione. Tuttavia, puoi fornire metadati aggiungendo elementi a questo tipo, dove un elemento è un'intestazione del metodo collocata nel corpo del tipo di annotazione.

Oltre a non avere corpi di codice, gli elementi sono soggetti alle seguenti restrizioni:

  • L'intestazione del metodo non può dichiarare parametri.
  • L'intestazione del metodo non può fornire una clausola throws.
  • Tipo di ritorno dell'intestazione metodo deve essere un tipo primitivo (es int), java.lang.String, java.lang.Class, un'enumerazione, un tipo di annotazione, oppure un array di uno di questi tipi. Nessun altro tipo può essere specificato per il tipo restituito.

Come altro esempio, il Listato 3 presenta un ToDotipo di annotazione con tre elementi che identificano un particolare lavoro di codifica, specificando la data in cui il lavoro deve essere terminato e nominando il programmatore responsabile del completamento del lavoro.

Listato 3:ToDo.java (versione 1)

public @interface ToDo {int id (); String finishDate (); String coder () predefinito "n / a"; }

Si noti che ogni elemento non dichiara alcun parametro o clausola throws, ha un tipo di ritorno legale ( into String) e termina con un punto e virgola. Inoltre, l'elemento finale rivela che è possibile specificare un valore di ritorno predefinito; questo valore viene restituito quando un'annotazione non assegna un valore all'elemento.

Il Listato 4 usa ToDoper annotare un metodo di classe non finito.

Listato 4:AnnDemo.java (versione 2)

public class AnnDemo {public static void main (String [] args) {String [] cities = {"New York", "Melbourne", "Beijing", "Moscow", "Paris", "London"}; sort (città); } @ToDo (id = 1000, finishDate = "10/10/2019", coder = "John Doe") static void sort (Object [] objects) {}}

Il Listato 4 assegna un elemento di metadati a ciascun elemento; ad esempio, 1000è assegnato a id. A differenza coderdegli elementi ide finishDatedevono essere specificati; in caso contrario, il compilatore segnalerà un errore. Quando codernon viene assegnato un valore, assume il "n/a"valore predefinito .

Java provides a special String value() element that can be used to return a comma-separated list of metadata items. Listing 5 demonstrates this element in a refactored version of ToDo.

Listing 5:ToDo.java (version 2)

public @interface ToDo { String value(); }

When value() is an annotation type’s only element, you don’t have to specify value and the = assignment operator when assigning a string to this element. Listing 6 demonstrates both approaches.

Listing 6:AnnDemo.java (version 3)

public class AnnDemo { public static void main(String[] args) { String[] cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(value = "1000,10/10/2019,John Doe") static void sort(Object[] objects) { } @ToDo("1000,10/10/2019,John Doe") static boolean search(Object[] objects, Object key) { return false; } }

Using meta-annotation types — the problem of flexibility

You can annotate types (e.g., classes), methods, local variables, and more. However, this flexibility can be problematic. For example, you might want to restrict ToDo to methods only, but nothing prevents it from being used to annotate other application elements, as demonstrated in Listing 7.

Listing 7:AnnDemo.java (version 4)

@ToDo("1000,10/10/2019,John Doe") public class AnnDemo { public static void main(String[] args) { @ToDo(value = "1000,10/10/2019,John Doe") String[] cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(value = "1000,10/10/2019,John Doe") static void sort(Object[] objects) { } @ToDo("1000,10/10/2019,John Doe") static boolean search(Object[] objects, Object key) { return false; } }

In Listing 7, ToDo is also used to annotate the AnnDemo class and cities local variable. The presence of these erroneous annotations might confuse someone reviewing your code, or even your own annotation processing tools. For the times when you need to narrow an annotation type’s flexibility, Java offers the Target annotation type in its java.lang.annotation package.

Target is a meta-annotation type — an annotation type whose annotations annotate annotation types, as opposed to a non-meta-annotation type whose annotations annotate application elements, such as classes and methods. It identifies the kinds of application elements to which an annotation type is applicable. These elements are identified by Target’s ElementValue[] value() element.

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

Java 5 ha introdotto uno aptstrumento per elaborare le annotazioni in modo generalizzato. Java 6 ha migrato aptla funzionalità nel suo javacstrumento di compilazione e Java 7 è stato deprecato apt, che è stato successivamente rimosso (a partire da Java 8).

Tipi di annotazioni standard

Insieme Target, Retention, Documented, e Inherited, Java 5 ha introdotto java.lang.Deprecated, java.lang.Overridee java.lang.SuppressWarnings. Questi tre tipi di annotazione sono progettati per essere utilizzati solo in un contesto del compilatore, motivo per cui i loro criteri di conservazione sono impostati su SOURCE.

Deprecato