Java Reflection: Inspecionando Fields
Inspecione, leia e modifique fields em tempo de execução em Java com a API de reflection.
Um objeto Field descreve um field de uma classe: seu nome, seu tipo, seus modificadores e — dado uma instância — seu valor. A reflection permite listar os fields de uma classe, lê-los e escrevê-los, mesmo quando são private e não possuem getter ou setter. É exatamente assim que desserializadores JSON populam objetos e como ORMs hidratam entidades. Este capítulo abrange como obter objetos Field, ler e escrever valores, o portão setAccessible, e o caso especial dos fields final.
Este capítulo se baseia na introdução à reflection. Para as APIs complementares, consulte inspecionando métodos e inspecionando construtores.
Obtendo objetos Field
A mesma divisão public-vs-declared da introdução se aplica:
Class<?> c = User.class;
Field f1 = c.getField("name"); // public field, incl. inherited — else NoSuchFieldException
Field f2 = c.getDeclaredField("name"); // any access level, this class only
Field[] all = c.getFields(); // public fields, incl. inherited
Field[] mine = c.getDeclaredFields(); // all access levels, this class onlygetField/getFields só enxergam fields public, mas seguem a cadeia de herança. getDeclaredField/getDeclaredFields enxergam fields private/protected/pacote também, mas apenas os que foram literalmente declarados na classe que você está consultando. Para coletar todos os fields incluindo os privados herdados, percorra getSuperclass() e mescle.
Metadados de Field: nome, tipo, modificadores, genéricos
Um Field responde perguntas sobre si mesmo sem precisar de nenhuma instância:
Field f = User.class.getDeclaredField("age");
f.getName(); // "age"
f.getType(); // int.class — the erased type
f.getGenericType(); // int — Type, keeps generic info
f.getModifiers(); // int bitset
Modifier.isPrivate(f.getModifiers()); // true/false
Modifier.isStatic(f.getModifiers());
Modifier.isFinal(f.getModifiers());
f.getDeclaringClass(); // class …UsergetType() retorna a Class apagada (List); getGenericType() retorna um Type que, para um field List<String>, você pode converter para ParameterizedType e recuperar String. Essa recuperação funciona porque as assinaturas genéricas de fields são preservadas no arquivo de classe, mesmo que as instâncias sejam apagadas.
Lendo e escrevendo valores
Para ler ou escrever, você precisa de uma instância (ou null para um field static) e deve ultrapassar a verificação de acesso:
User u = new User("ada", 36);
Field age = User.class.getDeclaredField("age");
age.setAccessible(true); // bypass the access check for private
int current = age.getInt(u); // typed getter for primitives → 36
age.setInt(u, 37); // typed setter
Object boxed = age.get(u); // generic getter, autoboxes → Integer 37
age.set(u, 40); // generic setter, autounboxesExistem acessores tipados — getInt, getBoolean, getDouble, setLong, … — para fields primitivos, e os genéricos get(Object)/set(Object,Object) para qualquer field (com boxing de primitivos). Para um field static, passe null como alvo: staticField.get(null).
O portão setAccessible
Por padrão, um Field aplica as regras de acesso do Java: ler um field private de forma reflexiva lança IllegalAccessException. field.setAccessible(true) suprime essa verificação para este objeto Field. É o que permite que a reflection acesse partes internas — e o que a torna perigosa.
Duas ressalvas desde o Java 9:
- Limites de módulo. Se o tipo alvo estiver em um módulo que não
opensseu pacote para você,setAccessible(true)lançaInaccessibleObjectException. Bibliotecas pedem que você adicione--add-opensou que o módulo façaopensdo pacote. - É por objeto. Chamar
setAccessible(true)afeta apenas a instância deFieldna qual você o chamou, não o field globalmente. UmFieldrecém-obtido para o mesmo membro começa bloqueado novamente.
Escrevendo fields final
Fields final são um caso especial e complicado. Para um field final não-estático, você pode às vezes ainda escrevê-lo após setAccessible(true):
Field f = Config.class.getDeclaredField("name"); // private final String
f.setAccessible(true);
f.set(config, "changed"); // may work…Mas há ressalvas importantes:
- Não funciona para constantes
static finalprimitivas ouString— essas são inlining pelo compilador em cada ponto de uso, portanto, mesmo que você altere o field, as leituras já compiladas não irão refletir isso. - A JVM e o JIT assumem que fields
finalnunca mudam; mutá-los é comportamento indefinido para visibilidade e pode ser otimizado para não ter efeito. - JDKs modernos proíbem isso cada vez mais completamente.
A regra honesta: não mute fields final de forma reflexiva em produção. Frameworks de serialização que fazem isso (para reconstruir objetos imutáveis) usam maquinaria de baixo nível Unsafe/VarHandle e aceitam o risco deliberadamente. O exemplo abaixo mostra o caso instance-final funcionando para ilustrar o mecanismo, não como recomendação.
Um exemplo prático: um mini mapeador baseado em fields
O programa reflete sobre os fields declarados de um objeto para construir um Map<String,Object> (um mini serializador), depois pega um map e escreve seus valores de volta em uma instância nova (um mini desserializador) — acessando fields private ao longo de todo o processo, sem nenhum getter ou setter em lugar algum.
O que extrair da execução:
toMapproduziu um snapshot de cada field de instância sem um único getter —getDeclaredFields()maissetAccessible(true)acessou o estadoprivatediretamente. Isso é, mecanicamente, o que Jackson e Gson fazem quando configurados para acesso por field. A classe não precisa de nenhuma API especial; a reflection fornece a genérica.- O field
staticcountfoi excluído porque o loop testouModifier.isStatic. Serializadores rotineiramente ignoram fieldsstatic,transiente sintéticos; o bitset de modificador é como você toma essas decisões uniformemente em vez de codificar nomes de fields. fromMapescreveu o fieldprivate final currencyapóssetAccessible(true)e surtiu efeito — demonstrando o mecanismo instance-final. Isso funcionou apenas porquecurrencyé um final não-estático reatribuído antes de qualquer otimizador assumir que era constante; depender disso em código real é frágil, e constantesstatic finalnão teriam cedido.- Ler metadados (
bal.getType(),Modifier.toString(...),isFinal(...)) não precisou de nenhuma instância deAccount— umFielddescreve a declaração, que é a mesma para todo objeto da classe. Valores precisam de uma instância; a estrutura não. - O
getInt(rebuilt)tipado retornou o primitivo diretamente sem boxing, e a leitura do fieldstaticusoucnt.get(null)— passarnullcomo alvo é a convenção para estáticos. Escolher o acessor tipado para primitivos evita uma alocação por leitura, o que importa em caminhos de serialização intensivos.