W3docs

Objetos Class em Java

Obtenha objetos Class<T> em Java com Object.getClass(), literais .class e Class.forName. Guia completo com exemplos.

Tudo em reflection começa a partir de um objeto Class. Para cada tipo que a JVM carrega — toda classe, interface, tipo array, enum, anotação e até cada primitivo — existe exatamente uma instância de Class que o descreve. Esse objeto é o seu acesso à estrutura do tipo: seu nome, sua superclasse, seus membros, suas anotações. Este capítulo aborda as três formas de obter um Class, o que Class<T> contém, e as pequenas surpresas que pegam as pessoas desprevenidas.

Três formas de obter um Class

Existem exatamente três caminhos, e cada um se adequa a uma situação diferente.

1. O literal .class — você conhece o tipo em tempo de compilação.

Class<String> c1 = String.class;
Class<int[]> c2 = int[].class;
Class<Integer> c3 = int.class == Integer.class ? null : int.class;   // see "primitives" below

Isso é seguro em tempo de compilação e o mais rápido — não há busca, o compilador incorpora uma referência direta. Use sempre que puder nomear o tipo.

2. Object.getClass() — você tem uma instância.

Object o = "hello";
Class<?> c = o.getClass();        // class java.lang.String

getClass() retorna a classe de runtime do objeto, que pode ser uma subclasse do tipo declarado da variável. Object o = new ArrayList<>() faz com que o.getClass() seja ArrayList.class, não Object.class. Seu tipo estático é Class<?> porque o compilador só sabe que o é algum Object.

3. Class.forName(String) — você tem apenas um nome.

Class<?> c = Class.forName("java.util.ArrayList");

Este é o caminho dinâmico: um nome de classe totalmente qualificado como string, resolvido em tempo de execução. Lança ClassNotFoundException se nenhuma classe com esse nome puder ser carregada. É o que carregadores de plugins e drivers JDBC usam. Existe uma variante carregada-mas-não-inicializada: Class.forName(name, false, classLoader) ignora os inicializadores estáticos até que a classe seja usada ativamente pela primeira vez.

O que Class<T> contém

Um objeto Class é uma descrição rica. Os métodos principais:

Class<?> c = ArrayList.class;

c.getName();              // "java.util.ArrayList"        (binary name)
c.getSimpleName();        // "ArrayList"
c.getCanonicalName();     // "java.util.ArrayList"        (source-like)
c.getPackageName();       // "java.util"
c.getSuperclass();        // class java.util.AbstractList
c.getInterfaces();        // [List, RandomAccess, Cloneable, Serializable]
c.getModifiers();         // int bitset → Modifier.isPublic(...) etc.
c.isInterface();          // false
c.isEnum(); c.isArray(); c.isPrimitive(); c.isAnnotation();

A partir de um Class você acessa cada membro: getDeclaredFields(), getDeclaredMethods(), getDeclaredConstructors(), além de seus equivalentes públicos/herdados get… (a separação do capítulo introdutório). Os capítulos seguintes aprofundam cada um.

Nome binário vs. nome simples vs. nome canônico

Os métodos de nomenclatura diferem de formas que causam problemas ao registrar ou comparar:

TipogetName()getSimpleName()getCanonicalName()
Stringjava.lang.StringStringjava.lang.String
int[][Iint[]int[]
String[][Ljava.lang.String;String[]java.lang.String[]
Map.Entry aninhadojava.util.Map$EntryEntryjava.util.Map.Entry
classe anônimaOuter$1"" (vazio)null

getName() é o nome binário — a forma interna da JVM, com $ para aninhamento e a codificação críptica [I / [L…; para arrays. É o que Class.forName espera. getCanonicalName() é a forma de código-fonte que você escreveria, e é null para tipos que não podem ser nomeados no código-fonte (locais, classes anônimas). Use getName() para round-trips com forName; use getSimpleName()/getCanonicalName() para saída legível por humanos.

Primitivos e arrays também têm objetos Class

Cada primitivo tem seu próprio Class, distinto do seu wrapper:

int.class == Integer.class      // false — two different Class objects
int.class.getName()             // "int"
Integer.TYPE == int.class       // true — TYPE is the primitive Class

void até tem void.class (e Void.TYPE). Classes de arrays são sintetizadas pela JVM: int[].class, String[][].class. arrayClass.getComponentType() remove uma dimensão (String[].class.getComponentType() é String.class). Essas distinções importam quando você compara tipos de parâmetros em getMethodgetMethod("foo", int.class) e getMethod("foo", Integer.class) encontram sobrecargas diferentes.

Identidade de classe e carregadores de classe

A identidade de um objeto Class não é apenas seu nome — é o par (nome, carregador de classe que o definiu). O mesmo arquivo .class carregado por dois carregadores de classe diferentes produz dois objetos Class distintos e incompatíveis. Uma conversão entre eles lança ClassCastException mesmo que os nomes sejam iguais. Isso é principalmente invisível em uma aplicação simples (um carregador), mas é a raiz de muitos enigmas "mas é a mesma classe!" em servidores de aplicação, OSGi e sistemas de hot-reload. Para reflection do dia a dia, trate objetos Class como singletons por tipo e compare-os com ==.

Um exemplo prático: sondando tipos de três formas

O programa obtém objetos Class por todos os três caminhos e, em seguida, interroga alguns tipos — uma classe comum, uma interface, um array, um primitivo e um tipo aninhado — para evidenciar as diferenças de nomenclatura e estrutura.

java— editable, runs on the server

O que aprender com a execução:

  • Todos os três caminhos convergiram para o mesmo tipo de objeto: um literal .class, uma chamada getClass() e uma pesquisa forName produziram um Class totalmente utilizável. O caminho que você escolhe depende do que você sabe (o tipo, uma instância ou apenas um nome) — o resultado é idêntico em capacidade.
  • getClass() na variável Greeter g retornou Robot, não Greeter. O tipo declarado é irrelevante; getClass() sempre reporta a classe concreta de runtime. É por isso que o despacho polimórfico e a inspeção reflexiva veem o mesmo tipo "real".
  • Os três métodos de nomenclatura divergiram exatamente onde a tabela prevê: String[] imprimiu o nome binário [Ljava.lang.String; de getName(), mas a forma legível String[] das formas simples e canônica. Se você precisar passar um nome de volta para forName, deve ser no formato getName().
  • int.class == Integer.class foi false enquanto Integer.TYPE == int.class foi true. O primitivo e seu wrapper são objetos Class distintos, e Integer.TYPE é apenas um alias para o primitivo. Confundi-los é a causa clássica de NoSuchMethodException ao pesquisar uma sobrecarga por tipo de parâmetro.
  • Robot.class == new Robot().getClass() foi true: dentro de um carregador de classe, um tipo é mapeado para exatamente um objeto Class, então == é a comparação correta. Você nunca precisa de .equals() em objetos Class em código de carregador único.

Armadilhas comuns

  • forName executa inicializadores estáticos (na sua forma de um argumento). Carregar uma classe pode ter efeitos colaterais. Use a forma de três argumentos com initialize=false se quiser apenas inspecionar.
  • getSimpleName() pode ser vazio (classes anônimas) e getCanonicalName() pode ser null (locais, anônimas). Não assuma que sempre são identificadores imprimíveis.
  • Genéricos são apagados. List<String>.class é ilegal; existe apenas List.class. Um Class não carrega informações de argumentos de tipo — essas ficam em Type/ParameterizedType, uma API de reflection separada (e mais avançada). Veja restrições de genéricos para entender por que o apagamento funciona assim.

Com o objeto Class em mãos, o próximo capítulo abre a primeira gaveta de membros: campos — inspecionando-os, lendo-os e escrevendo neles, mesmo quando são privados ou finais.

Prática

Prática
Você tem 'Object o = new java.util.LinkedList<String>();' declarado como 'Object'. Você chama 'o.getClass().getName()'. Qual string é retornada, e por que não é 'java.lang.Object'?
Você tem 'Object o = new java.util.LinkedList<String>();' declarado como 'Object'. Você chama 'o.getClass().getName()'. Qual string é retornada, e por que não é 'java.lang.Object'?
Was this page helpful?