W3docs

Introdução ao Java Reflection

O que é reflection em Java, quando usá-lo e uma visão geral do pacote java.lang.reflect.

Reflection é a capacidade de um programa de inspecionar e manipular sua própria estrutura em tempo de execução: perguntar a uma classe quais campos, métodos e construtores ela possui, ler e escrever esses campos, chamar esses métodos e criar novas instâncias — tudo sem nomear os tipos em tempo de compilação. Enquanto o Java comum é estático (o compilador conhece todos os tipos que você usa), reflection é dinâmico (você descobre tipos a partir de strings, configurações ou do que estiver carregado em tempo de execução). Esta parte do livro é um tour pelo java.lang.reflect; este capítulo apresenta o cenário.

O que reflection permite fazer

O código normal nomeia o tipo com o qual trabalha:

User u = new User("ada");
String name = u.getName();

O código reflexivo chega ao mesmo resultado sem escrever User ou getName como tokens em tempo de compilação — eles são strings resolvidas em tempo de execução:

Class<?> cls = Class.forName("com.example.User");
Object u = cls.getDeclaredConstructor(String.class).newInstance("ada");
Object name = cls.getMethod("getName").invoke(u);

A segunda forma é muito mais verbosa e muito mais lenta, e descarta a verificação de tipos em tempo de compilação. Você jamais a escreveria para lógica de aplicação comum. Você recorre a ela precisamente quando o tipo não é conhecido até o tempo de execução.

Quando reflection vale a pena

Reflection é o motor por trás de frameworks, não do código do dia a dia. Usos típicos:

  • Injeção de dependência (Spring, Guice, CDI): o container lê anotações e assinaturas de construtores, depois instancia e conecta beans cujo código-fonte nunca viu.
  • Serialização (Jackson, Gson): uma biblioteca JSON percorre os campos de um objeto para lê-los ou preenchê-los sem que você escreva código de mapeamento por classe.
  • ORMs (Hibernate, JPA): mapeiam colunas para campos refletindo sobre uma classe de entidade.
  • Executores de testes (JUnit): encontram métodos anotados com @Test e os invocam.
  • Sistemas de plugins: carregam uma classe nomeada em um arquivo de configuração e chamam um método de interface conhecido nela.

O fio comum: um mecanismo geral operando sobre tipos de usuário arbitrários contra os quais não foi compilado. Esse é exatamente o problema que reflection resolve.

Os tipos principais em java.lang.reflect

Reflection começa a partir de um objeto Class (abordado no próximo capítulo, Java Class Objects) e se ramifica em uma pequena família de classes, cada uma descrevendo um tipo de membro:

TipoRepresentaObtido de Class via
Fieldum campogetField / getDeclaredField(s)
Methodum métodogetMethod / getDeclaredMethod(s)
Constructor<T>um construtorgetConstructor / getDeclaredConstructor(s)
Parameterum parâmetro de método/construtorExecutable.getParameters()
Modifierum helper para o bitset int de modificadoresmétodos estáticos

Field, Method e Constructor compartilham uma hierarquia de superclasse comum: Member (a interface) e AccessibleObject (que carrega setAccessible). Essa base compartilhada é por isso que "torná-lo acessível" e "ler suas anotações" parecem idênticos independentemente do membro que você possui.

A distinção entre get… e getDeclared…

Quase todo método de busca existe em dois sabores, e a distinção importa em todos os capítulos seguintes:

  • getField / getMethod / getConstructor — retorna membros públicos, incluindo os herdados de superclasses e interfaces.
  • getDeclaredField / getDeclaredMethod / getDeclaredConstructor — retorna membros de qualquer nível de acesso (private, protected, pacote), mas apenas aqueles declarados nesta classe exata — nada herdado.

Portanto, getMethods() vê um método público herdado de um pai, mas não um helper private na própria classe; getDeclaredMethods() vê o helper private, mas não o método público herdado. Para acessar um membro privado herdado, você percorre getSuperclass() chamando getDeclared… a cada nível.

O custo: velocidade, segurança e encapsulamento

Reflection é poderoso, e cada poder tem um preço.

  • Desempenho. Chamadas reflexivas são mais lentas que chamadas diretas — buscas de método/campo, verificações de acesso e boxing de argumentos adicionam sobrecarga. O JIT otimiza bem chamadas reflexivas frequentes, mas reflection em um loop apertado é um mau sinal. Armazene os objetos Method/Field em cache; nunca os busque por chamada.
  • Sem segurança em tempo de compilação. Um erro de digitação no nome de um método compila bem e falha em tempo de execução com NoSuchMethodException. Ferramentas de refatoração renomeiam getName em todo lugar — exceto dentro da sua string "getName".
  • Quebrando o encapsulamento. setAccessible(true) permite ler e escrever estado private. É assim que serializadores preenchem campos sem setter, mas isso acopla você a internos que o autor da classe nunca prometeu manter estáveis.
  • Restrições de módulo. Desde o Java 9, o sistema de módulos pode negar acesso reflexivo a pacotes não exportados. Chamar setAccessible(true) além de um limite de módulo que não fez opens no pacote lança InaccessibleObjectException.

Um exemplo prático: um pequeno dumper genérico de objetos

Para tornar o escopo concreto, aqui está uma rotina reflexiva que imprime os campos e seus valores de qualquer objeto — o tipo de coisa que um depurador ou uma biblioteca de logging faz. Ela não nomeia nenhum tipo de aplicação; funciona com qualquer coisa que for passada.

java— editable, runs on the server

O que extrair da execução:

  • O método dump não nomeou nenhum tipo concreto e ainda assim imprimiu tanto Point quanto User. Seu único contrato é "me dê um Object"; tudo sobre a estrutura — nomes de campos, tipos, valores — veio de getClass() em tempo de execução. Esse é o movimento definidor do reflection: uma rotina, entradas arbitrárias.
  • getDeclaredFields() retornou todos os campos, incluindo os private, mas lê-los exigiu setAccessible(true) primeiro. Sem essa chamada, f.get(obj) em um campo privado lança IllegalAccessException. A busca e o acesso são duas barreiras separadas.
  • Modifier.toString(f.getModifiers()) converteu o bitset bruto de modificadores em texto legível como private final. Modificadores são armazenados como um int de bits de flag; o helper Modifier os decodifica para que você não precise testar bits manualmente.
  • O terceiro objeto foi construído sem nenhum new User(...) em nenhum lugar no código-fonte — Class.forName("…$User") resolveu a classe aninhada a partir de uma string (note o separador $ para tipos aninhados), e getDeclaredConstructor(...).newInstance(...) o construiu. Este é o padrão de carregamento de plugins em miniatura: um nome entra, um objeto sai.
  • Ler o valor do campo (f.get(obj)) e ler os metadados do campo (f.getName(), f.getModifiers()) são independentes. Metadados não precisam de instância nem de acessibilidade; valores precisam do objeto e, para campos privados, do flag de acessibilidade.

Como o restante desta parte está organizado

Cada capítulo restante aprofunda um aspecto:

  • Class objects — as três maneiras de obter um Class<T> e o que ele informa.
  • Fields — inspecionando, lendo e escrevendo campos (incluindo private e final).
  • Methods — encontrando e invocando métodos, resolução de sobrecarga, valores de retorno.
  • Constructors — construindo instâncias de forma reflexiva, incluindo construtores privados.
  • Annotations — lendo os metadados que você anexou com anotações, em tempo de execução.
  • Dynamic proxies — sintetizando implementações completas de interface em tempo de execução.

Ao longo de tudo, mantenha o trade-off em mente: reflection é a ferramenta certa quando o tipo genuinamente não é conhecido até o tempo de execução, e a ferramenta errada quando é. O próximo capítulo começa na raiz de tudo — o objeto Class.

Prática

Prática
Uma biblioteca de logging precisa imprimir os valores dos campos de qualquer objeto passado ao seu método 'log(Object o)', incluindo objetos cujas classes ela nunca foi compilada contra e cujos campos são 'private'. Qual combinação é a abordagem mínima correta?
Uma biblioteca de logging precisa imprimir os valores dos campos de qualquer objeto passado ao seu método 'log(Object o)', incluindo objetos cujas classes ela nunca foi compilada contra e cujos campos são 'private'. Qual combinação é a abordagem mínima correta?
Was this page helpful?