Java 51: Annotations & Reflection Basics
easy⏱ 5 mincoursejava
Declarar anotaciones y retención
@interface Test {} declara una anotación. @Retention decide su vida útil: SOURCE (solo compilador, p. ej. @Override), CLASS (en el .class, por defecto, no en runtime), o RUNTIME (legible vía reflection — requerido por frameworks). @Target restringe dónde va (METHOD, TYPE, FIELD). Las anotaciones pueden tener elementos con defaults: @Test(timeout = 1000).
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test { long timeout() default 0; }
class MyTests {
@Test void addsCorrectly() { /* ... */ }
}
Reflection: leer clases en runtime
Class<?> c = obj.getClass() es el punto de entrada. c.getDeclaredMethods(), getDeclaredFields(), getConstructors() enumeran miembros. method.isAnnotationPresent(Test.class) chequea una anotación; method.getAnnotation(Test.class) lee sus elementos. method.invoke(instance, args...) la llama dinámicamente. Así un test runner encuentra y corre métodos @Test sin que los registres.
for (Method m : MyTests.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(Test.class)) {
m.invoke(new MyTests()); // dynamic call
}
}
Los costos de reflection
Reflection es más lento que llamadas directas (sin inlining del JIT, chequeos de seguridad), evita los chequeos de tipo en compilación (los errores aparecen en runtime como InvocationTargetException) y setAccessible(true) rompe el encapsulamiento. Úsalo para cableado a nivel framework, no en hot paths. Alternativas modernas — MethodHandle, processors de anotaciones (codegen en compilación) y records — reducen la necesidad de reflection en runtime.
Construye un test runner reflectivo
Marca métodos con un flag estilo @Test (modela la anotación RUNTIME como metadatos registrados). Escribe runTests(instance) que descubra cada método de test, lo invoque, capture aserciones lanzadas y cuente pass/fail. Incluye un test que pasa y uno que falla. Imprime un resumen estilo JUnit: Tests run: N, Failures: M y el estado de cada test.
Prefiere annotation processors por rendimiento
El escaneo por reflection en runtime tiene un costo de arranque (el classpath scan de Spring). Los processors de anotaciones (javax.annotation.processing) corren en compilación y generan código plano — Dagger, Micronaut y Lombok usan esto para cableado sin reflection y arranque más rápido. Si controlas el build, el codegen en compilación gana a la reflection en runtime.
Quiz: ¿por qué @Retention(RUNTIME)?
Una anotación propia no es visible para la reflection de tu framework — ¿por qué? Probablemente le falta @Retention(RUNTIME). La retención por defecto es CLASS, que mantiene la anotación en el bytecode pero la descarta antes de runtime, así que isAnnotationPresent devuelve false. Cualquier anotación que un framework lea reflectivamente debe ser @Retention(RetentionPolicy.RUNTIME).