Reflexión en Java: inspeccionar clases en tiempo de ejecución
La API que hizo posible Spring y Hibernate también es impenetrable y lenta si se abusa. Cómo funciona, qué coste tiene y qué alternativas modernas la sustituyen.
La reflexión es la capacidad de una programa de mirarse al espejo: preguntar por sus propias clases, métodos y campos mientras se ejecuta. En Java llegó con la versión 1.1 y sostuvo dos décadas de frameworks —Spring inyecta dependencias con ella, JPA mapea entidades con ella, los serializadores la usan sin pedir permiso—. También es la API favorita de los tutors para hacer magia ilegible. Este artículo la recorre con ejemplos que compilan, le pone precio a cada truco y muestra las alternativas que el lenguaje moderno ha ido construyendo a su lado.
El punto de entrada: Class
Todo empieza con un objeto Class<?>, el plano de una clase en memoria. Hay tres maneras de conseguirlo, y la tercera es la que distingue a la reflexión de la vida normal:
Class<String> a = String.class; // literal de clase
Class<?> b = "hola".getClass(); // desde una instancia
Class<?> c = Class.forName("java.time.LocalDate"); // por nombreRecorrer la anatomía de una clase
Class<?> c = Class.forName("es.revista.Usuario");
for (Field f : c.getDeclaredFields()) {
System.out.printf("%s %s%n",
f.getType().getSimpleName(), f.getName());
}
for (Constructor<?> k : c.getConstructors()) {
System.out.println(k.getParameterCount() + " parametro(s)");
}
for (Method m : c.getDeclaredMethods()) {
System.out.println(m.getReturnType().getSimpleName()
+ " " + m.getName());
}La diferencia entre getX y getDeclaredX da más disgustos que cualquier otra parte de la API: la versión sin Declared recorre solo miembros públicos, incluidos los heredados; getDeclaredFields() devuelve todos los de la clase, también los privados, pero ninguno heredado. Para mapa completo hay que subir por la jerarquía a mano.
Instanciar e invocar: la magia completa
Constructor<?> ctor = c.getConstructor(String.class);
Object u = ctor.newInstance("Ada");
Method getNombre = c.getMethod("getNombre");
Object nombre = getNombre.invoke(u); // "Ada"
Field campo = c.getDeclaredField("nombre");
campo.setAccessible(true); // derriba el private
campo.set(u, "Grace");
System.out.println(getNombre.invoke(u)); // "Grace"setAccessible(true) es el momento en que la reflexión deja de observar y empieza a invadir: ignora el control de acceso y toca campos privados. Desde JDK 17 y el sistema de módulos, ese derribo está restringido: abrir los paquetes internos del JDK exige flags de arranque (--add-opens), y buena parte de las migraciones dolorosas a versiones recientes han sido exactamente esto: frameworks obligados a dejar de manosear java.lang.
El precio de la magia
- Velocidad: una invocación reflexiva es un chequeo de seguridad más una búsqueda de método más un boxing de argumentos. Con JIT caliente la diferencia se estrecha, pero nunca es gratis; los frameworks compilan hoy proxies o generan código en vez de reflexionar en caliente.
- Compile-time: nada te avisa si el método cambia de nombre —todo falla en ejecución, en producción, en viernes.
- Herramientas: el código reflexivo es invisible para los renombradores, los buscadores de usos y el análisis estático.
Las alternativas modernas
El lenguaje ha ido absorbiendo los usos legítimos de la reflexión. Para datos, record (Java 16) ofrece introspección limpia y sin sorpresas:
public record Usuario(String nombre, int edad) {}
Usuario u = new Usuario("Ada", 36);
System.out.println(u.nombre()); // accesor directo, sin magia
RecordComponent[] rcs = Usuario.class.getRecordComponents();
for (RecordComponent rc : rcs) {
System.out.println(rc.getName() + " " + rc.getType());
}Para invocación dinámica seria, MethodHandle (Java 7) es la alternativa de bajo nivel que el propio JDK usa: más verboso, pero más rápido y con comprobaciones en el punto de creación. Y para comparar campos y escribir equals/hashCode/toString, la reflexión ha sido simplemente jubilada por los records, que generan todo eso en el compilador.
Regla práctica
Usa reflexión para escribir herramientas y frameworks; usa records y tipos normales para escribir aplicaciones. Si tu código de negocio importa java.lang.reflect, casi siempre hay un diseño mejor esperando.
La reflexión es también la bisagra con otros trucos de la plataforma: la generación de PDF con iText la evita con APIs declarativas, el juego de la serpiente no la necesita en absoluto, y el histórico puente con JavaScript era puro reflejo por debajo.