W3docs

Oggetti Class in Java

Ottieni oggetti Class<T> in Java con Object.getClass(), letterali .class e Class.forName.

Tutto ciò che riguarda la reflection parte da un oggetto Class. Per ogni tipo caricato dalla JVM — ogni classe, interfaccia, tipo array, enum, annotazione e persino ogni primitivo — esiste esattamente un'istanza di Class che lo descrive. Quell'oggetto è il tuo riferimento alla struttura del tipo: il suo nome, la sua superclasse, i suoi membri, le sue annotazioni. Questo capitolo tratta i tre modi per ottenere un Class, cosa contiene Class<T> e le piccole sorprese che colgono di sorpresa gli sviluppatori.

Tre modi per ottenere un Class

Esistono esattamente tre percorsi, ognuno adatto a una situazione diversa.

1. Il letterale .class — conosci il tipo in fase di compilazione.

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

Questo è sicuro in fase di compilazione e il più veloce — non c'è nessuna ricerca, il compilatore incorpora un riferimento direttamente. Usalo ogni volta che puoi nominare il tipo.

2. Object.getClass() — hai un'istanza.

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

getClass() restituisce la classe a runtime dell'oggetto, che può essere una sottoclasse del tipo dichiarato della variabile. Object o = new ArrayList<>() fa sì che o.getClass() == ArrayList.class, non Object.class. Il suo tipo statico è Class<?> perché il compilatore sa solo che o è qualche Object.

3. Class.forName(String) — hai solo un nome.

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

Questo è il percorso dinamico: un nome di classe completamente qualificato come stringa, risolto a runtime. Lancia ClassNotFoundException se nessuna classe con quel nome è caricabile. È ciò che usano i plugin loader e i driver JDBC. Esiste una variante caricata-ma-non-inizializzata: Class.forName(name, false, classLoader) salta gli inizializzatori statici finché la classe non viene usata attivamente per la prima volta.

Cosa contiene Class<T>

Un oggetto Class è una descrizione ricca. I metodi principali:

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();

Da un Class si raggiunge ogni membro: getDeclaredFields(), getDeclaredMethods(), getDeclaredConstructors(), più le loro controparti pubbliche/ereditate get… (la distinzione introdotta nel capitolo introduttivo). I capitoli successivi approfondiscono ciascuno.

Nome binario vs. nome semplice vs. nome canonico

I metodi di denominazione differiscono in modi che possono causare problemi quando si registrano o confrontano:

TipogetName()getSimpleName()getCanonicalName()
Stringjava.lang.StringStringjava.lang.String
int[][Iint[]int[]
String[][Ljava.lang.String;String[]java.lang.String[]
nested Map.Entryjava.util.Map$EntryEntryjava.util.Map.Entry
anonymous classOuter$1"" (empty)null

getName() è il nome binario — la forma interna della JVM, con $ per l'annidamento e la codifica criptica [I / [L…; per gli array. È ciò che si aspetta Class.forName. getCanonicalName() è la forma sorgente che scriveresti, ed è null per i tipi che non puoi nominare nel sorgente (locali, classi anonime). Usa getName() per i round-trip con forName; usa getSimpleName()/getCanonicalName() per l'output leggibile.

Anche i primitivi e gli array hanno oggetti Class

Ogni primitivo ha il proprio Class, distinto dal suo wrapper:

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

Anche void ha void.class (e Void.TYPE). Le classi array vengono sintetizzate dalla JVM: int[].class, String[][].class. arrayClass.getComponentType() rimuove una dimensione (String[].class.getComponentType() è String.class). Queste distinzioni contano quando abbini i tipi di parametri in getMethodgetMethod("foo", int.class) e getMethod("foo", Integer.class) trovano overload diversi.

Identità della classe e class loader

L'identità di un oggetto Class non è solo il suo nome — è la coppia (nome, class loader definente). Lo stesso file .class caricato da due class loader diversi produce due oggetti Class distinti e incompatibili. Un cast tra di essi lancia ClassCastException anche se i nomi corrispondono. Questo è per lo più invisibile in un'applicazione normale (un solo loader) ma è alla radice di molti enigmi "ma è la stessa classe!" in application server, OSGi e sistemi di hot-reload. Per la reflection quotidiana, tratta gli oggetti Class come singleton per tipo e confrontali con ==.

Un esempio pratico: analizzare i tipi in tre modi

Il programma ottiene oggetti Class attraverso tutti e tre i percorsi, quindi interroga una manciata di tipi — una classe normale, un'interfaccia, un array, un primitivo e un tipo annidato — per evidenziare le differenze di denominazione e strutturali.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Tutti e tre i percorsi hanno prodotto lo stesso tipo di oggetto: un letterale .class, una chiamata a getClass() e una ricerca con forName hanno ciascuno restituito un Class completamente utilizzabile. Il percorso che scegli dipende da cosa sai (il tipo, un'istanza o solo un nome) — il risultato è identico in termini di capacità.
  • getClass() sulla variabile Greeter g ha restituito Robot, non Greeter. Il tipo dichiarato è irrilevante; getClass() riporta sempre la classe concreta a runtime. Ecco perché il dispatch polimorfico e l'ispezione riflessiva vedono lo stesso tipo "reale".
  • I tre metodi di denominazione hanno divergito esattamente dove la tabella prevede: String[] ha stampato il nome binario [Ljava.lang.String; da getName() ma la forma leggibile String[] dalle forme semplice e canonica. Se mai vuoi reinserire un nome in forName, deve essere nella forma di getName().
  • int.class == Integer.class era false mentre Integer.TYPE == int.class era true. Il primitivo e il suo wrapper sono oggetti Class distinti, e Integer.TYPE è solo un alias per quello primitivo. Confonderli è la causa classica di NoSuchMethodException quando si cerca un overload per tipo di parametro.
  • Robot.class == new Robot().getClass() era true: all'interno di un singolo class loader, un tipo corrisponde esattamente a un oggetto Class, quindi == è il confronto corretto. Non hai mai bisogno di .equals() sugli oggetti Class nel codice con un singolo loader.

Errori comuni

  • forName esegue gli inizializzatori statici (nella sua forma con un argomento). Il caricamento di una classe può avere effetti collaterali. Usa la forma con tre argomenti con initialize=false se vuoi solo ispezionare.
  • getSimpleName() può essere vuoto (classi anonime) e getCanonicalName() può essere null (locali, anonime). Non dare per scontato che siano sempre identificatori stampabili.
  • I generici vengono cancellati. List<String>.class non è valido; esiste solo List.class. Un Class non porta informazioni sugli argomenti di tipo — queste si trovano in Type/ParameterizedType, un'API di reflection separata (e più avanzata). Vedi restrizioni sui generici per capire come funziona l'erasure.

Con l'oggetto Class in mano, il capitolo successivo apre il primo cassetto dei membri: i campi — come ispezionarli, leggerli e scriverli, anche quando sono privati o final.

Esercitazione

Pratica
Hai 'Object o = new java.util.LinkedList<String>();' dichiarato come 'Object'. Chiami 'o.getClass().getName()'. Quale stringa viene restituita, e perché non è 'java.lang.Object'?
Hai 'Object o = new java.util.LinkedList<String>();' dichiarato come 'Object'. Chiami 'o.getClass().getName()'. Quale stringa viene restituita, e perché non è 'java.lang.Object'?
Was this page helpful?