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 elementsSi 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
@Cachedjunto a un método no almacena nada en caché. Algo más tiene que buscar@Cachedy 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.@Overridey@SuppressWarningsfuncionan así; el bytecode no contiene ningún registro de ellas.CLASS— la anotación se escribe en el archivo.classpero 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 depackage-info.java.TYPE_USE— cualquier 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:
- El compilador. Las anotaciones integradas como
@Override,@SafeVarargsy@FunctionalInterfaceson verificadas por el propiojavac. - 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 frameworkMicronautfuncionan de esta manera. - Reflexión en tiempo de ejecución.
Method.getAnnotations(),Class.getAnnotation(...), etc. devuelven las instancias de anotaciones para cualquier elemento con retenciónRUNTIME. 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.
Lo que se puede extraer de la ejecución:
- El bucle a nivel de clase vio
@DeprecatedenGreeterpero nada más —@Deprecatedtiene retenciónRUNTIME.@Overridey@SuppressWarningssonSOURCE, 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ó
@DeprecatedenoldHello. AunquetoStringse declaró con@Overrideycastse declaró con@SuppressWarnings("unchecked"), ninguna anotación llegó al archivo de clase. La información existía en el momento en quejavacse ejecutó — así es como ocurrió la verificación de sobreescritura — y luego fue descartada. - La verificación de retención lo aclaró:
@Overridey@SuppressWarningsllevan@Retention(SOURCE)en sus propias declaraciones, mientras que@Deprecatedlleva@Retention(RUNTIME). La retención es una propiedad del tipo de anotación, no de cómo se usa. - Leer
@DeprecatedenGreetermediantecls.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), usaSOURCE. Si un framework necesita leer la anotación mientras el programa se ejecuta (DI, ORM, enlace JSON), usaRUNTIME. 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."