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
@Teste 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:
| Tipo | Representa | Obtido de Class via |
|---|---|---|
Field | um campo | getField / getDeclaredField(s) |
Method | um método | getMethod / getDeclaredMethod(s) |
Constructor<T> | um construtor | getConstructor / getDeclaredConstructor(s) |
Parameter | um parâmetro de método/construtor | Executable.getParameters() |
Modifier | um helper para o bitset int de modificadores | mé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/Fieldem 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 renomeiamgetNameem todo lugar — exceto dentro da sua string"getName". - Quebrando o encapsulamento.
setAccessible(true)permite ler e escrever estadoprivate. É 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 fezopensno pacote lançaInaccessibleObjectException.
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.
O que extrair da execução:
- O método
dumpnão nomeou nenhum tipo concreto e ainda assim imprimiu tantoPointquantoUser. Seu único contrato é "me dê umObject"; tudo sobre a estrutura — nomes de campos, tipos, valores — veio degetClass()em tempo de execução. Esse é o movimento definidor do reflection: uma rotina, entradas arbitrárias. getDeclaredFields()retornou todos os campos, incluindo osprivate, mas lê-los exigiusetAccessible(true)primeiro. Sem essa chamada,f.get(obj)em um campo privado lançaIllegalAccessException. A busca e o acesso são duas barreiras separadas.Modifier.toString(f.getModifiers())converteu o bitset bruto de modificadores em texto legível comoprivate final. Modificadores são armazenados como umintde bits de flag; o helperModifieros 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), egetDeclaredConstructor(...).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
privateefinal). - 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.