1. Creación de objetos mediante reflexión
A veces necesitamos crear objetos sin conocer su tipo en tiempo de compilación. Por ejemplo, el nombre de la clase llega desde la configuración, o escribimos un framework genérico (serializador, contenedor DI). En código habitual escribiríamos:
User user = new User("Ivan", 25);
Pero ¿y si no sabemos que la clase se llama User ni los parámetros de su constructor? Aquí ayuda la reflexión.
Método obsoleto: Class.newInstance()
Antes en Java existía el método Class.newInstance(), que creaba un objeto mediante el constructor público sin parámetros:
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();
IMPORTANTE: Este método está marcado como deprecated desde Java 9 y no se recomienda. No permite elegir el constructor, oculta las causas de los errores y exige únicamente un constructor public sin parámetros.
Método actual: mediante constructores
En código real no siempre hay un constructor public sin parámetros. El enfoque actual es obtener el constructor necesario mediante getConstructor(...) o getDeclaredConstructor(...), y después llamar a newInstance(...).
Ejemplo: creación de un objeto con parámetros
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
import java.lang.reflect.Constructor;
Class<?> clazz = Class.forName("User");
// Obtenemos el constructor con parámetros String, int
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// Creamos el objeto y pasamos los parámetros
Object user = constructor.newInstance("Ivan", 25);
System.out.println(user); // toString, si está definido
¿Qué ocurre aquí?
- Obtenemos el constructor necesario por su firma.
- Invocamos newInstance(...) en él, pasando los argumentos.
- Obtenemos un objeto de tipo Object (se puede hacer cast a User si el tipo es conocido).
¿Y si el constructor es privado?
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // ¡Magia! Ahora se puede invocar el constructor privado.
Object user = constructor.newInstance("Ivan", 25);
ATENCIÓN: No conviene abusar de esto — se rompe la encapsulación. En aplicaciones modulares (Java 9+) también existen restricciones de acceso.
Tabla comparativa de formas de crear objetos
| Método | Velocidad | Seguridad de tipos | Cuándo usar |
|---|---|---|---|
|
La más rápida | Completa | Siempre que sea posible |
|
Lenta | Se pierde | Frameworks, plugins |
|
Media | Parcial | Bibliotecas de alto rendimiento |
| Fábricas | Rápida | Completa | Creación flexible de objetos |
2. Invocación de métodos mediante reflexión
Puede invocar cualquier método de un objeto, incluso si no conoce su nombre en tiempo de compilación o si es private.
Obtención de un método
import java.lang.reflect.Method;
Class<?> clazz = user.getClass();
// Obtenemos un método public por nombre y tipos de parámetros
Method method = clazz.getMethod("getName"); // Sin parámetros
Object result = method.invoke(user); // Invocación del método sin parámetros
System.out.println(result); // Imprimirá el nombre del usuario
Invocación de un método con parámetros
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Petr"); // Establecemos un nuevo nombre
Invocación de un método privado
Method secret = clazz.getDeclaredMethod("secretMethod", int.class);
secret.setAccessible(true); // Quitamos la protección
Object secretResult = secret.invoke(user, 123);
Importante: Todos los parámetros se pasan como un array de objetos (varargs). Si el método devuelve algo, el resultado llegará como Object.
3. Acceso a campos mediante reflexión
A veces hay que leer o modificar el valor de un campo, incluso si es private (por ejemplo, en serialización o pruebas).
Obtención de un campo
import java.lang.reflect.Field;
Class<?> clazz = user.getClass();
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // Si el campo no es public
// Lectura del valor
Object age = ageField.get(user);
System.out.println("Edad: " + age);
// Modificación del valor
ageField.set(user, 42);
System.out.println("Nueva edad: " + ageField.get(user));
Trabajo con campos estáticos
Si el campo es estático, pase en get/set null en lugar del objeto:
Field staticField = clazz.getDeclaredField("counter");
staticField.setAccessible(true);
staticField.set(null, 100); // Para campos static no se necesita objeto
4. Limitaciones y excepciones
El trabajo con reflexión puede ir acompañado de muchas excepciones comprobadas (checked). Las más comunes:
- ClassNotFoundException — la clase con ese nombre no se encuentra.
- NoSuchMethodException — no hay constructor o método con esa firma.
- NoSuchFieldException — campo no encontrado.
- IllegalAccessException — sin acceso al miembro (por ejemplo, método privado sin setAccessible(true)).
- InstantiationException — la clase es abstracta o es una interfaz; no se puede crear un objeto.
- InvocationTargetException — error dentro del constructor o método invocado.
Ejemplo de manejo:
try {
// ... su código con reflexión ...
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
Si los detalles no importan, es más cómodo capturar la superclase común ReflectiveOperationException (Java 7+).
5. Práctica: miniaplicación «Instanciador dinámico»
Ejemplo de clase para experimentar
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private void sayHello() {
System.out.println("Hola, me llamo " + name + " y tengo " + age + " años.");
}
}
Creación y manipulación dinámicas
import java.lang.reflect.*;
public class ReflectionDemo {
public static void main(String[] args) {
try {
// 1. Obtenemos el objeto Class por nombre
Class<?> clazz = Class.forName("Person");
// 2. Obtenemos el constructor y creamos el objeto
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object person = constructor.newInstance("Alisa", 30);
// 3. Invocamos el método privado sayHello
Method sayHello = clazz.getDeclaredMethod("sayHello");
sayHello.setAccessible(true);
sayHello.invoke(person); // Hola, me llamo Alisa y tengo 30 años.
// 4. Cambiamos el campo privado name
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person, "Bob");
// 5. Volvemos a invocar sayHello
sayHello.invoke(person); // Hola, me llamo Bob y tengo 30 años.
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
Tenga en cuenta:
- Todo se hace sin conocimiento directo del tipo Person en el código.
- Se pueden modificar campos privados e invocar métodos privados (si lo permiten la política de seguridad de la JVM y la configuración modular).
6. ¿Cómo se relaciona esto con aplicaciones reales?
La reflexión está en la base de muchas bibliotecas y frameworks populares:
- JUnit: búsqueda de métodos con la anotación @Test, creación de instancias de tests e invocaciones de métodos.
- Spring: DI, creación de beans, autowiring.
- Jackson, Gson: serialización/deserialización de campos de objetos.
- Hibernate: acceso a campos de entidades, proxies y cargas perezosas.
7. Esquema visual: cómo se crea un objeto mediante reflexión
flowchart TB
A["Nombre de la clase (String)"] --> B["Class.forName"]
B --> C["Class<?>"]
C --> D["getConstructor(...)"]
D --> E["Constructor<?>"]
E --> F["newInstance(...)"]
F --> G["Object"]
8. Errores típicos al trabajar con reflexión
Error n.º 1: Constructor incorrecto. Solicita un constructor con una firma equivocada, por ejemplo, getConstructor(String.class) cuando solo existe un constructor con dos parámetros — obtendrá NoSuchMethodException. ¡Revise siempre las firmas!
Error n.º 2: Falta de acceso. Invocar un constructor/método privado sin setAccessible(true) conduce a IllegalAccessException. Y recuerde: en Java 9+ el sistema de módulos puede imponer restricciones adicionales.
Error n.º 3: Problemas de tipos. Todos los parámetros y valores de retorno mediante reflexión son Object. Un casting incorrecto — ClassCastException.
Error n.º 4: Excepciones no manejadas. La reflexión lanza muchas excepciones checked. Manéjelas, por ejemplo, con la general ReflectiveOperationException; de lo contrario, el código no compilará.
Error n.º 5: Violación de la encapsulación. El uso indiscriminado de setAccessible(true) — «acceder a la nevera ajena». Úselo solo cuando haya una necesidad real y teniendo en cuenta los requisitos de seguridad.
GO TO FULL VERSION