Cómo describir el código Java con anotaciones

Probablemente haya encontrado situaciones en las que necesite asociar metadatos (datos que describen otros datos) con clases, métodos y / u otros elementos de la aplicación. Por ejemplo, es posible que su equipo de programación necesite identificar las clases sin terminar en una aplicación grande. Para cada clase sin terminar, los metadatos probablemente incluirían el nombre del desarrollador responsable de terminar la clase y la fecha de finalización esperada de la clase.

Antes de Java 5, los comentarios eran el único mecanismo flexible que Java tenía para ofrecer para asociar metadatos con elementos de la aplicación. Sin embargo, los comentarios son una mala elección. Debido a que el compilador los ignora, los comentarios no están disponibles en tiempo de ejecución. E incluso si estuvieran disponibles, el texto tendría que analizarse para obtener elementos de datos cruciales. Sin estandarizar cómo se especifican los elementos de datos, estos elementos de datos podrían resultar imposibles de analizar.

descargar Obtener el código Descargue el código fuente para ver ejemplos en este tutorial de Java 101. Creado por Jeff Friesen para.

Mecanismos de anotación no estándar

Java proporciona mecanismos no estándar para asociar metadatos con elementos de la aplicación. Por ejemplo, la transientpalabra reservada le permite anotar (asociar datos con) campos que se excluirán durante la serialización.

Java 5 cambió todo al introducir anotaciones , un mecanismo estándar para asociar metadatos con varios elementos de la aplicación. Este mecanismo consta de cuatro componentes:

  • Un @interfacemecanismo para declarar tipos de anotaciones.
  • Tipos de metaanotación, que puede utilizar para identificar los elementos de la aplicación a los que se aplica un tipo de anotación; para identificar la duración de una anotación (una instancia de un tipo de anotación); y más.
  • Soporte para el procesamiento de anotaciones a través de una extensión de la API de Java Reflection (que se discutirá en un artículo futuro), que puede usar para descubrir las anotaciones en tiempo de ejecución de un programa, y ​​una herramienta generalizada para procesar anotaciones.
  • Tipos de anotaciones estándar.

Explicaré cómo utilizar estos componentes a medida que avanzamos en este artículo.

Declaración de tipos de anotaciones con @interface

Puede declarar un tipo de anotación especificando el @símbolo seguido inmediatamente por la interfacepalabra reservada y un identificador. Por ejemplo, el Listado 1 declara un tipo de anotación simple que puede usar para anotar código seguro para subprocesos.

Listado 1:ThreadSafe.java

public @interface ThreadSafe {}

Después de declarar este tipo de anotación, anteponga los métodos que considere seguros para subprocesos con instancias de este tipo anteponiendo @inmediatamente seguido del nombre del tipo a los encabezados del método. El Listado 2 ofrece un ejemplo simple donde main()se anota el método @ThreadSafe.

Listado 2:AnnDemo.java (versión 1)

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

ThreadSafelas instancias no proporcionan metadatos más que el nombre del tipo de anotación. Sin embargo, puede proporcionar metadatos agregando elementos a este tipo, donde un elemento es un encabezado de método colocado en el cuerpo del tipo de anotación.

Además de no tener cuerpos de código, los elementos están sujetos a las siguientes restricciones:

  • El encabezado del método no puede declarar parámetros.
  • El encabezado del método no puede proporcionar una cláusula throws.
  • Tipo de retorno de la cabecera del método debe ser un tipo primitivo (por ejemplo, int), java.lang.String, java.lang.Class, una enumeración, un tipo de anotación, o una matriz de uno de estos tipos. No se puede especificar ningún otro tipo para el tipo de devolución.

Como otro ejemplo, el Listado 3 presenta un ToDotipo de anotación con tres elementos que identifican un trabajo de codificación en particular, que especifican la fecha en que se terminará el trabajo y nombran al codificador responsable de completar el trabajo.

Listado 3:ToDo.java (versión 1)

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

Tenga en cuenta que cada elemento no declara parámetro (s) o cláusula throws, tiene un tipo de retorno legal ( into String) y termina con un punto y coma. Además, el elemento final revela que se puede especificar un valor de retorno predeterminado; este valor se devuelve cuando una anotación no asigna un valor al elemento.

El Listado 4 se utiliza ToDopara anotar un método de clase sin terminar.

Listado 4:AnnDemo.java (versión 2)

public class AnnDemo {public static void main (String [] args) {String [] cities = {"Nueva York", "Melbourne", "Beijing", "Moscú", "París", "Londres"}; sort (ciudades); } @ToDo (id = 1000, finishDate = "10/10/2019", codificador = "John Doe") clasificación vacía estática (objetos [] objetos) {}}

El Listado 4 asigna un elemento de metadatos a cada elemento; por ejemplo, 1000está asignado a id. A diferencia coder, los elementos idy finishDatedeben especificarse; de lo contrario, el compilador informará de un error. Cuando coderno se le asigna un valor, asume su "n/a"valor predeterminado .

Java proporciona un String value()elemento especial que se puede utilizar para devolver una lista de elementos de metadatos separados por comas. El Listado 5 demuestra este elemento en una versión refactorizada de ToDo.

Listado 5:ToDo.java (versión 2)

public @interface ToDo {String value (); }

Cuando value()es el único elemento de un tipo de anotación, no tiene que especificar valueni el =operador de asignación al asignar una cadena a este elemento. El Listado 6 demuestra ambos enfoques.

Listado 6:AnnDemo.java (versión 3)

public class AnnDemo {public static void main (String [] args) {String [] cities = {"Nueva York", "Melbourne", "Beijing", "Moscú", "París", "Londres"}; sort (ciudades); } @ToDo (value = "1000,10 / 10/2019, John Doe") static void sort (Object [] objects) {} @ToDo ("1000,10 / 10/2019, John Doe") búsqueda booleana estática ( Objeto [] objetos, clave de objeto) {devolver falso; }}

Uso de tipos de metaanotación: el problema de la flexibilidad

Puede anotar tipos (por ejemplo, clases), métodos, variables locales y más. Sin embargo, esta flexibilidad puede resultar problemática. Por ejemplo, es posible que desee restringir ToDosolo a métodos, pero nada impide que se use para anotar otros elementos de la aplicación, como se muestra en el Listado 7.

Listado 7:AnnDemo.java (versión 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 [] ciudades = {"Nueva York", "Melbourne", "Beijing", "Moscú", "París", "Londres"}; sort (ciudades); } @ToDo (value = "1000,10 / 10/2019, John Doe") static void sort (Object [] objects) {} @ToDo ("1000,10 / 10/2019, John Doe") búsqueda booleana estática ( Objeto [] objetos, clave de objeto) {devolver falso; }}

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 introdujo una aptherramienta para procesar anotaciones de manera generalizada. Java 6 migró aptla funcionalidad a su javacherramienta de compilación, y Java 7 aptquedó en desuso , que posteriormente se eliminó (comenzando con Java 8).

Tipos de anotaciones estándar

Junto con Target, Retention, Documented, y Inherited, Java 5 introdujeron java.lang.Deprecated, java.lang.Overridey java.lang.SuppressWarnings. Estos tres tipos de anotaciones están diseñados para usarse en un contexto de compilador únicamente, por lo que sus políticas de retención están configuradas en SOURCE.

Obsoleto