W3docs

Introducción a las Anotaciones en Java

Qué son las anotaciones en Java, cómo adjuntan metadatos al código y dónde son procesadas.

Una anotación es una marca que se adjunta a un elemento del código fuente Java — una clase, método, campo, parámetro, uso de tipo — que añade metadatos. La anotación en sí misma 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 cual reacciona. @Override en un método le indica al compilador "estoy sobreescribiendo un método de la superclase; por favor avisa 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 lleva información; el comportamiento reside en el procesador que la lee.

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

La forma de una anotación

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

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

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

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

Algunas anotaciones tienen elementos (su versión de campos). Los valores de los elementos 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, puedes 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 bandera, no una función.
  • Las anotaciones no son comentarios. Los comentarios desaparecen en tiempo de compilación; las anotaciones son construcciones del lenguaje de primera clase. Participan en el sistema de tipos, pueden requerirse que permanezcan en el archivo de clase y pueden leerse en tiempo de ejecución mediante reflexión.
  • Las anotaciones no son un sustituto del código claro. Una larga pila de anotaciones encima de una clase es densidad de información, no siempre claridad. Los frameworks que dependen mucho de las anotaciones (Spring, JPA, JAX-RS) pagan la conveniencia 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 tiempo sobreviven los metadatos:

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

Verás la meta-anotación @Retention(...) que establece esta política en el capítulo de Meta-anotaciones. En resumen: elige 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 objetivo — el conjunto de elementos del programa que puede anotar legalmente. Los objetivos más comunes:

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

Si colocas una anotación en un lugar donde su @Target no lo permite, el compilador rechaza la operación. Intentar @Override en una declaración de clase es un error de compilación porque @Override está dirigida a METHOD.

Quién lee las anotaciones

Tres lugares consumen datos de anotaciones:

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

Los dos primeros no necesitan soporte de máquina virtual más allá de lo 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 práctico: inspeccionando anotaciones en tu propia clase

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

java— editable, runs on the server

Lo que se puede extraer 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, por lo 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ó con @Override y cast se declaró con @SuppressWarnings("unchecked"), ninguna anotación llegó al archivo de clase. La información existía en el momento en que javac se ejecutó — así es como ocurrió la verificación de sobreescritura — y luego fue descartada.
  • La verificación de retención lo aclaró: @Override y @SuppressWarnings llevan @Retention(SOURCE) en sus propias declaraciones, 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 mediante cls.getAnnotation(Deprecated.class) devolvió un proxy cuyos métodos de elemento (since(), forRemoval()) retornaron los valores escritos en el código fuente. Esa es la interfaz en tiempo de ejecución para los metadatos de anotaciones: una instancia cuyos elementos son métodos de acceso.
  • La conclusión para elegir la retención: si el único consumidor es javac (verificaciones de sobreescritura, supresión de advertencias), usa SOURCE. Si un framework necesita leer la anotación mientras el programa se ejecuta (DI, ORM, enlace JSON), usa RUNTIME. El capítulo sobre meta-anotaciones cubre cómo declarar esto para tus propios tipos de anotaciones.

Lo que viene en esta parte

Los capítulos restantes de esta parte te llevan a través de:

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

El recorrido va desde "qué anotaciones ofrece el lenguaje" hasta "qué anotaciones puedes construir sobre ellas."

Práctica

Práctica
Un compañero escribe `@Cached` junto a un método y espera que la segunda llamada devuelva un resultado almacenado en caché. ¿Qué entendió mal sobre las anotaciones?
Un compañero escribe `@Cached` junto a un método y espera que la segunda llamada devuelva un resultado almacenado en caché. ¿Qué entendió mal sobre las anotaciones?
Was this page helpful?