CodeGym /Cursos /JAVA 25 SELF /Criação de objetos com reflexão

Criação de objetos com reflexão

JAVA 25 SELF
Nível 62 , Lição 3
Disponível

1. Criação de objetos com reflexão

Às vezes precisamos criar objetos sem conhecer seu tipo em tempo de compilação. Por exemplo, o nome da classe vem de uma configuração, ou estamos escrevendo um framework genérico (serializador, contêiner de DI). Em código comum, escreveríamos:

User user = new User("Ivan", 25);

Mas e se não soubermos que a classe se chama User e não conhecermos os parâmetros do seu construtor? É aí que a reflexão ajuda.

Abordagem obsoleta: Class.newInstance()

Antigamente, o Java tinha o método Class.newInstance(), que cria um objeto por meio de um construtor público sem parâmetros:

Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();

IMPORTANTE: Esse método foi marcado como deprecated desde o Java 9 e não é recomendado. Ele não permite escolher o construtor, oculta as causas dos erros e exige apenas um public-construtor sem parâmetros.

Abordagem moderna: via construtores

No código real, nem sempre existe um public-construtor sem parâmetros. A abordagem atual é obter o construtor necessário via getConstructor(...) ou getDeclaredConstructor(...) e então chamar newInstance(...) nele.

Exemplo: criar um objeto com 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");
// Obtemos o construtor com parâmetros String, int
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// Criamos o objeto, passamos os parâmetros
Object user = constructor.newInstance("Ivan", 25);

System.out.println(user); // toString, se definido

O que está acontecendo aqui?

  • Obtemos o construtor necessário pela assinatura.
  • Chamamos newInstance(...) nele, passando os argumentos.
  • Recebemos um objeto do tipo Object (pode ser convertido para User, se o tipo for conhecido).

E se o construtor for privado?

Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // Mágica! Agora é possível chamar o construtor privado.
Object user = constructor.newInstance("Ivan", 25);

ATENÇÃO: Evite o uso excessivo disso — quebra a encapsulação. Em aplicativos modulares (Java 9+), também existem restrições de acesso.

Tabela comparativa das formas de criar objetos

Forma Velocidade Segurança de tipos Quando usar
new User()
A mais rápida Total Sempre que possível
Constructor.newInstance()
Lenta Perdida Frameworks, plugins
MethodHandle
Média Parcial Bibliotecas de alto desempenho
Fábricas Rápida Total Criação flexível de objetos

2. Invocação de métodos via reflexão

Você pode invocar qualquer método de um objeto, mesmo sem conhecer seu nome em tempo de compilação, ou se ele for private.

Obtendo um método

import java.lang.reflect.Method;

Class<?> clazz = user.getClass();
// Obtemos um método public pelo nome e tipos de parâmetro
Method method = clazz.getMethod("getName"); // Sem parâmetros
Object result = method.invoke(user); // Chamada de método sem parâmetros

System.out.println(result); // Irá imprimir o nome do usuário

Invocando um método com parâmetros

Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Petr"); // Define um novo nome

Invocando um método privado

Method secret = clazz.getDeclaredMethod("secretMethod", int.class);
secret.setAccessible(true); // Remove a restrição
Object secretResult = secret.invoke(user, 123);

Importante: Todos os parâmetros são passados como um array de objetos (varargs). Se o método retornar algo — o resultado virá como Object.

3. Acesso a campos via reflexão

Às vezes é necessário ler ou alterar o valor de um campo, mesmo que ele seja private (por exemplo, em serialização ou testes).

Obtendo um campo

import java.lang.reflect.Field;

Class<?> clazz = user.getClass();
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // Se o campo não for public

// Leitura do valor
Object age = ageField.get(user);
System.out.println("Idade: " + age);

// Alteração do valor
ageField.set(user, 42);
System.out.println("Nova idade: " + ageField.get(user));

Trabalhando com campos estáticos

Se o campo for estático, passe null para get/set em vez do objeto:

Field staticField = clazz.getDeclaredField("counter");
staticField.setAccessible(true);
staticField.set(null, 100); // Para campos static, não é necessário objeto

4. Restrições e exceções

O trabalho com reflexão pode gerar muitas checked‑exceptions. As mais comuns:

  • ClassNotFoundException — a classe com esse nome não foi encontrada.
  • NoSuchMethodException — não há construtor ou método com essa assinatura.
  • NoSuchFieldException — campo não encontrado.
  • IllegalAccessException — sem acesso ao membro (por exemplo, método privado sem setAccessible(true)).
  • InstantiationException — a classe é abstrata ou é uma interface; não é possível criar o objeto.
  • InvocationTargetException — erro dentro do construtor ou método invocado.

Exemplo de tratamento:

try {
    // ... seu código com reflexão ...
} catch (ReflectiveOperationException e) {
    e.printStackTrace();
}

Se os detalhes não importarem, é mais conveniente capturar a superclasse comum ReflectiveOperationException (Java 7+).

5. Prática: miniaplicativo “Instanciador Dinâmico”

Exemplo de classe para experimentos

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("Olá, meu nome é " + name + ", tenho " + age + " anos.");
    }
}

Criação e manipulação dinâmicas

import java.lang.reflect.*;

public class ReflectionDemo {
    public static void main(String[] args) {
        try {
            // 1. Obtemos o objeto Class pelo nome
            Class<?> clazz = Class.forName("Person");

            // 2. Obtemos o construtor e criamos o objeto
            Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
            Object person = constructor.newInstance("Alice", 30);

            // 3. Invocamos o método privado sayHello
            Method sayHello = clazz.getDeclaredMethod("sayHello");
            sayHello.setAccessible(true);
            sayHello.invoke(person); // Olá, meu nome é Alice, tenho 30 anos.

            // 4. Alteramos o campo privado name
            Field nameField = clazz.getDeclaredField("name");
            nameField.setAccessible(true);
            nameField.set(person, "Bob");

            // 5. Invocamos sayHello novamente
            sayHello.invoke(person); // Olá, meu nome é Bob, tenho 30 anos.

        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

Observe:

  • Tudo é feito sem conhecimento direto do tipo Person no código.
  • É possível alterar campos privados e invocar métodos privados (se isso for permitido pela política de segurança da JVM e pela configuração de módulos).

6. Como isso se relaciona com aplicações reais?

A reflexão está na base de muitas bibliotecas e frameworks populares:

  • JUnit: busca de métodos com a anotação @Test, criação de instâncias de testes e invocação de métodos.
  • Spring: DI, criação de beans, autowiring.
  • Jackson, Gson: serialização/desserialização de campos de objetos.
  • Hibernate: acesso a campos de entidades, proxies e carregamento lazy.

7. Esquema visual: como um objeto é criado via reflexão

flowchart TB
    A["Nome da classe (String)"] --> B["Class.forName"]
    B --> C["Class<?>"]
    C --> D["getConstructor(...)"]
    D --> E["Constructor<?>"]
    E --> F["newInstance(...)"]
    F --> G["Object"]

8. Erros comuns ao trabalhar com reflexão

Erro nº 1: construtor errado. Solicitar um construtor com assinatura incorreta, por exemplo, getConstructor(String.class) quando há apenas um construtor com dois parâmetros — você receberá NoSuchMethodException. Sempre verifique as assinaturas!

Erro nº 2: falta de acesso. Invocar um construtor/método privado sem setAccessible(true) leva a IllegalAccessException. E lembre-se: no Java 9+, o sistema de módulos pode impor restrições adicionais.

Erro nº 3: problemas de tipos. Todos os parâmetros e valores de retorno via reflexão — são Object. Um cast incorreto resulta em ClassCastException.

Erro nº 4: exceções não tratadas. A reflexão lança muitas checked‑exceptions. Trate-as, por exemplo, com a superclasse ReflectiveOperationException; caso contrário, o código não irá compilar.

Erro nº 5: violação de encapsulamento. Uso indiscriminado de setAccessible(true) — “acesso à geladeira alheia”. Use apenas quando realmente necessário e leve em conta os requisitos de segurança.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION