W3docs

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 only

getField/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 …User

getType() 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, autounboxes

Existem 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 opens seu pacote para você, setAccessible(true) lança InaccessibleObjectException. Bibliotecas pedem que você adicione --add-opens ou que o módulo faça opens do pacote.
  • É por objeto. Chamar setAccessible(true) afeta apenas a instância de Field na qual você o chamou, não o field globalmente. Um Field recé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 final primitivas ou String — 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 final nunca 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.

java— editable, runs on the server

O que extrair da execução:

  • toMap produziu um snapshot de cada field de instância sem um único getter — getDeclaredFields() mais setAccessible(true) acessou o estado private diretamente. 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 static count foi excluído porque o loop testou Modifier.isStatic. Serializadores rotineiramente ignoram fields static, transient e sintéticos; o bitset de modificador é como você toma essas decisões uniformemente em vez de codificar nomes de fields.
  • fromMap escreveu o field private final currency após setAccessible(true) e surtiu efeito — demonstrando o mecanismo instance-final. Isso funcionou apenas porque currency é 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 constantes static final não teriam cedido.
  • Ler metadados (bal.getType(), Modifier.toString(...), isFinal(...)) não precisou de nenhuma instância de Account — um Field descreve 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 field static usou cnt.get(null) — passar null como 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.

Prática

Prática
Uma biblioteca JSON desserializa em uma classe que tem fields 'private' e sem setters. Usando reflection, ela chama 'getDeclaredField(name)' e depois 'field.set(obj, value)', mas recebe 'IllegalAccessException' no primeiro field privado. Adicionar qual única chamada resolve isso, e por que ela é necessária?
Uma biblioteca JSON desserializa em uma classe que tem fields 'private' e sem setters. Usando reflection, ela chama 'getDeclaredField(name)' e depois 'field.set(obj, value)', mas recebe 'IllegalAccessException' no primeiro field privado. Adicionar qual única chamada resolve isso, e por que ela é necessária?
Was this page helpful?