CodeGym /Cursos /JAVA 25 SELF /Creación de objetos mediante reflexión

Creación de objetos mediante reflexión

JAVA 25 SELF
Nivel 62 , Lección 3
Disponible

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
new User()
La más rápida Completa Siempre que sea posible
Constructor.newInstance()
Lenta Se pierde Frameworks, plugins
MethodHandle
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.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION