W3docs

Introducción a las anotaciones de Java

Qué son las anotaciones de Java, cómo adjuntan metadatos al código y dónde se procesan.

Introducción a las anotaciones de Java

Una anotación es un marcador que adjunta a un fragmento de código fuente Java — una clase, método, campo, parámetro o uso de tipo — y que añade metadatos. La anotación en sí no hace nada. Es una etiqueta que otro fragmento de código (el compilador, un framework, un IDE, una herramienta de compilación) lee más tarde y ante la que reacciona. @Override en un método le dice al compilador: «estoy sobrescribiendo un método de la superclase; protesta si no es así». @Test en un método le dice a JUnit: «esto es una prueba». @Entity en una clase le dice a JPA: «mapea esto a una tabla de base de datos». En todos los casos la anotación solo transporta información; el comportamiento vive en el procesador que la lee.

Ya ha visto anotaciones a lo largo de este libro — @Override en métodos sobrescritos, @FunctionalInterface en interfaces de un solo método, @SuppressWarnings para silenciar al compilador. Esta parte del libro trata del sistema que hay debajo: qué es una anotación, cuándo está disponible (solo en el fuente, en el archivo de clase o en tiempo de ejecución), cómo escribir las suyas y cómo las consumen los procesadores.

La forma de una anotación

Sintácticamente, una anotación es el símbolo @ seguido del nombre de la anotación, aplicado inmediatamente antes de aquello que anota:

@Override
public String toString() { ... }

@Deprecated
public void oldApi() { ... }

@SuppressWarnings("unchecked")
List<String> list = (List<String>) raw;

Algunas anotaciones toman elementos (su versión de los campos). Los valores de elemento van entre paréntesis:

@SuppressWarnings("unchecked")                       // one element, value "unchecked"
@SuppressWarnings({"unchecked", "rawtypes"})         // array of strings

@RequestMapping(path = "/users", method = GET)       // two named elements

Si una anotación declara un único elemento llamado value, puede omitir el nombre — @SuppressWarnings("unchecked") es la forma abreviada de @SuppressWarnings(value = "unchecked").

Lo que las anotaciones no son

Tres negaciones que evitan los malentendidos más comunes:

  • Las anotaciones no ejecutan código. Escribir @Cached junto a un método no almacena nada en caché. Algo más tiene que buscar @Cached y añadir el comportamiento de caché. La anotación es una marca, no una función.
  • Las anotaciones no son comentarios. Los comentarios desaparecen al compilar; las anotaciones son construcciones del lenguaje de pleno derecho. Participan en el sistema de tipos, pueden estar obligadas a vivir en el archivo de clase y pueden leerse en tiempo de ejecución mediante reflexión.
  • Las anotaciones no sustituyen al código claro. Una larga pila de anotaciones sobre una clase es densidad de información, no siempre claridad. Los frameworks que se apoyan mucho en las anotaciones (Spring, JPA, JAX-RS) pagan la comodidad con una curva de aprendizaje y un coste en tiempo de ejecución.

Cuándo vive la anotación

Cada anotación tiene una política de retención que decide cuánto sobreviven los metadatos:

  • SOURCE — el compilador la lee y luego la descarta. @Override y @SuppressWarnings funcionan así; el bytecode no conserva ningún rastro de ellas.
  • CLASS — la anotación se escribe en el archivo .class pero la JVM no la carga en tiempo de ejecución. Este es el valor por defecto. Las herramientas que inspeccionan el bytecode (analizadores estáticos, posprocesadores) pueden leerla.
  • RUNTIME — la anotación se conserva hasta el final; la reflexión puede pedirle a cualquier clase, método o campo sus anotaciones en tiempo de ejecución. Frameworks como Spring y Jackson dependen de esto.

Verá la meta-anotación @Retention(...) que fija esta política en el capítulo Meta-anotaciones. En resumen: elija la retención según quién necesita leer la anotación — el compilador, una herramienta en tiempo de compilación o el código en tiempo de ejecución.

Dónde puede ir la anotación

Cada anotación también tiene un destino (target) — el conjunto de elementos de programa que puede anotar legalmente. Los destinos comunes:

  • TYPE — clases, interfaces, enums.
  • METHOD — métodos.
  • FIELD — campos.
  • PARAMETER — parámetros de método.
  • CONSTRUCTOR — constructores.
  • LOCAL_VARIABLE — declaraciones de variables locales.
  • ANNOTATION_TYPE — otras declaraciones de anotación (meta-anotaciones).
  • PACKAGE — paquetes, vía package-info.java.
  • TYPE_USEcualquier uso de un tipo, incluidos los parámetros genéricos y los casts (Java 8+).

Si coloca una anotación donde su @Target no lo permite, el compilador la rechaza. Intentar @Override en una declaración de clase es un error de compilación porque @Override apunta a METHOD.

Quién lee las anotaciones

Tres lugares consumen los datos de anotación:

  1. El compilador. Las anotaciones integradas como @Override, @SafeVarargs y @FunctionalInterface las comprueba el propio javac.
  2. Procesadores de anotaciones. Herramientas conectables de tiempo de compilación que se ejecutan durante javac. Pueden leer las anotaciones de los fuentes que se compilan y generar en respuesta nuevos archivos de código fuente. Lombok, Dagger, el metamodelo estático de Hibernate y el framework Micronaut funcionan todos así.
  3. Reflexión en tiempo de ejecución. Method.getAnnotations(), Class.getAnnotation(...), etc. devuelven las instancias de anotación de cualquier elemento con retención RUNTIME. Así decide Spring qué inyectar, así encuentra JUnit sus pruebas y así mapea Jackson el JSON.

Los dos primeros no necesitan más soporte de la máquina virtual que el que ofrece javac. El tercero necesita que la anotación esté escrita en el archivo de clase y cargada por el entorno de ejecución.

Un ejemplo resuelto: inspeccionar las anotaciones de su propia clase

El objetivo de este ejemplo es mostrar que @Override, @Deprecated y @SuppressWarnings lucen idénticas en el fuente pero se comportan de forma distinta una vez compilada la clase. El programa declara una clase con varias anotaciones y luego le pregunta a la reflexión qué puede ver realmente.

java— editable, runs on the server

Qué llevarse de la ejecución:

  • El bucle a nivel de clase vio @Deprecated en Greeter pero nada más — @Deprecated tiene retención RUNTIME. @Override y @SuppressWarnings son SOURCE, así que el compilador las eliminó antes de escribir el archivo de clase y la reflexión no puede recuperarlas.
  • El bucle por método solo imprimió @Deprecated en oldHello. Aunque toString se declaró @Override y cast se declaró @SuppressWarnings("unchecked"), ninguna anotación llegó al archivo de clase. La información existió en el momento en que se ejecutó javac — así ocurrió la comprobación de sobrescritura — y luego se descartó.
  • La comprobación de retención lo dejó claro: @Override y @SuppressWarnings llevan @Retention(SOURCE) en su propia declaración, mientras que @Deprecated lleva @Retention(RUNTIME). La retención es una propiedad del tipo de anotación, no de cómo se usa.
  • Leer @Deprecated en Greeter vía cls.getAnnotation(Deprecated.class) devolvió un proxy cuyos métodos de elemento (since(), forRemoval()) devolvieron los valores escritos en el fuente. Esa es la interfaz en tiempo de ejecución hacia los metadatos de anotación: una instancia cuyos elementos son métodos de acceso.
  • La conclusión para elegir la retención: si el único consumidor es javac (comprobaciones de sobrescritura, supresión de advertencias), use SOURCE. Si un framework necesita leer la anotación mientras el programa se ejecuta (DI, ORM, enlace JSON), use RUNTIME. El capítulo sobre meta-anotaciones cubre cómo declarar esto para sus propios tipos de anotación.

Lo que viene en esta parte

Los capítulos restantes de esta parte le guían a través de:

  • Las anotaciones integradas más comunes en java.lang@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface.
  • Las meta-anotaciones que configuran las suyas — @Retention, @Target, @Documented, @Inherited, @Repeatable.
  • La escritura de tipos de anotación personalizados y su lectura mediante reflexión.
  • La API de procesamiento de anotaciones en tiempo de compilación que los frameworks usan para generar código.

El arco va de «qué anotaciones le da el lenguaje» a «qué anotaciones puede construir sobre ellas».

Practice

Práctica

A teammate writes `@Cached` next to a method and expects the second call to return a cached result. What did they get wrong about annotations?