L'API
reflection
In
generale la reflection non va usata alla leggera: la
sua utilità è riservata a specifici campi
applicativi, ed esistono molti abili programmatori che
non ne hanno mai fatto uso. Chi desidera studiare questo
potente strumento dovrebbe per prima cosa aver chiara
la struttura del linguaggio a livello meta-descrittivo.
Si osservi la figura 1:
Figura 1 - Meta rappresentazione UML
Una
classe, che in bitecode Java può rappresentare
anche un'interfaccia, un array, una enum o un tipo di
annotazione, può contenere al suo interno una
collezione di campi, di costruttori, di metodi o di
altri oggetti class (ad esempio classi o interfacce
interne). Ogni campo è caratterizzato da un nome
simbolico, un tipo (il cui valore viene espresso in
termini di oggetti class) e un valore, che può
essere un oggetto qualunque. I metodi sono caratterizzati
da un nome, un valore di ritorno, una collezione di
tipi di parametro ed una di eccezioni. Inoltre la classe
Method dispone di un metodo invoke(), che permette di
chiamare quel particolare metodo su un oggetto appartenente
alla classe base. I costruttori sono simili ai metodi,
ma non hanno valore di ritorno; al posto del metodo
invoke() dispongono di un metodo newInstance(), che
permette di creare nuovi oggetti di quella particolare
classe.
La
visione offerta fino ad ora è molto vicina alla
realtà, ma per forza di cose incompleta. I dettagli
verranno affrontati dapprima attraverso lo studio delle
classi che compongono l'API reflection, e quindi attraverso
una serie di esempi.
Nota:
con l'arrivo di Java 5 l'API reflection ha subito una
serie di aggiunte così pervasive da non permettere
uno studio differenziato di quanto era possibile fare
un tempo e quanto è invece possibile fare oggi.
L'aggiunta dei tipi parametrici non ha solo comportato
l'aggiunta di nuovi metodi di ispezione: alcune classi
dell'API sono state riformulate come classi parametriche.
Pertanto l'API a cui si farà riferimento nel
presente trattato è quella offerta a partire
dalla versione 1.5 del JSDK.
La
classe Class<T>
La classe Class<T> è una classe parametrica,
il cui parametro T denota la classe rappresentata dall'oggetto
stesso. Un oggetto Class può rappresentare classi,
interfacce, array, enum e tipi di annotazione. Persino
void e i tipi primitivi sono rappresentati da oggetti
Class.
Il
primo metodo permette di ottenere una classe a partire
dal nome completo di percorso di package (ad esempio
java.awt.Button):
static
Class<?> forName(String className)
Un
gruppo di metodi premette di ottenere il nome della
classe in vari formati:
String
getSimpleName()
String getCanonicalName()
String getName()
Il
primo restituisce il nome semplice della classe, quello
dichiarato nel sorgente, mentre il secondo riporta il
nome completo di percorso di package e delle eventuali
classi contenitore (ad esempio it.mokabyte.MyClass$MyInnerClass).
Il terzo si comporta in modo differente a seconda del
tipo di elemento rappresentato dall'oggetto Class in
questione. Se l'oggetto rappresenta un elemento diverso
da un array, allora viene restituito il nome binario
della classe (ad esempio java.util.Vector). Se l'oggetto
rappresenta void o un tipo primitivo, viene restituita
la keyword Java corrispondente. Infine se l'oggetto
rappresenta un array, viene restituita una stringa corrispondente
al nome del tipo di elementi contenuti nell'array, preceduti
da un numero di caratteri '[' pari alle dimensioni dell'array
stesso (ad esempio '[Ljava.lang.String'). Nella Tavola
1 sono riportati tutti i possibili elementi e la relativa
codifica simbolica:
Tavola 1: Codifica simbolica usata dal metodo
getName() di Class
Una
coppia di metodi permette di ottenere un link alla superclasse
o un vettore di oggetti Class corrispondenti alle interfacce
implementate:
Class<?
super T> getSuperclass()
Class[] getInterfaces()
Un'altra
coppia di metodi permette di gestire il caso di superclassi
o interfacce parametriche:
Type[]
getGenericInterfaces()
Type getGenericSuperclass()
Una
serie di metodi permette di conoscere se il corrente
oggetto Class è un valore primitivo, un'interfaccia,
un'array, una classe locale, una classe anonima, una
classe interna, una enum o un'annotazione:
boolean
isPrimitive()
boolean isInterface()
boolean isArray()
boolean isLocalClass()
boolean isAnonymousClass()
boolean isMemberClass()
boolean isEnum()
boolean isAnnotation()
Se
la classe rappresenta un array, il seguente metodo restituisce
il tipo degli oggetti contenuti nell'array stesso:
Class<?>
getComponentType()
Un
gruppo di metodi permette di accedere ai vari elementi
public della classe, compresi quelli presenti nelle
superclassi, sotto forma di array. Quando un elemento
non è presente, viene restituito un vettore vuoto:
Package
getPackage()
Field[] getFields()
Constructor[] getConstructors()
Method[] getMethods()
Class[] getClasses()
T[] getEnumConstants()
I
seguenti metodi assomigliano ai precedenti, ma con due
differenze: anzitutto restituiscono tutti gli elementi
dichiarati, indipendentemente dal modificatore di protezione
utilizzato; in secondo luogo essi restituiscono solamente
gli elementi dichiarati nel presente oggetto Class,
ma non quelli presenti nelle superclassi. Anche in questo
caso, se un elemento non è presente, viene restituito
un vettore vuoto:
Field[]
getDeclaredFields()
Constructor[] getDeclaredConstructors()
Method[] getDeclaredMethods()
Class[] getDeclaredClasses()
Il
prossimo metodo permette di conoscere gli eventuali
tipi parametrici dichiarati nella presente classe:
TypeVariable<Class<T>>[]
getTypeParameters()
La
classe TypeVariable verrà approfondita più
avanti. I seguenti metodi permettono di accedere ad
un particolare campo, costruttore o metodo fornendo
il nome e/o l'elenco del tipo dei parametri, sotto forma
di oggetti Class. Anche in questo caso vale la differenza
tra i metodi getXxx() e getDeclaredXxx():
Field
getField(String name)
Constructor<T> getConstructor(Class... parameterTypes)
Method getMethod(String name, Class... parameterTypes)
Field getDeclaredField(String name)
Constructor<T> getDeclaredConstructor(Class...
parameterTypes)
Method getDeclaredMethod(String name, Class... parameterTypes)
Il
seguente metodo restituisce un intero che rappresenta
i modificatori della presente classe o interfaccia:
int
getModifiers()
La
classe java.lang.reflect.Modifier contiene le costanti
numeriche corrispondenti ai vari modificatori, nonché
una serie di metodi di interrogazione per sapere a quale
modificatore corrisponde l'intero restituito dal metodo
precedente. Si noti che ogni oggetto Class può
disporre di più di un modificatore: infatti l'intero
è l'OR logico dei valori corrispondenti ai singoli
modificatori, ognuno dei quali ha un valore corrispondente
ad un bit diverso.
I
seguenti metodi riguardano nello specifico le classi
interne o anonime, e permettono di ottenere la classe,
il metodo o il costruttore contenitore:
Class<?>
getDeclaringClass()
Class<?> getEnclosingClass()
Constructor<?> getEnclosingConstructor()
Method getEnclosingMethod()
I
metodi seguenti rappresentano la versione dinamica degli
operatori new, instanceof e dell'operatore di cast;
il metodo asSubclass() in particolare effettua un'operazione
di casting alla superclasse specificata:
T
newInstance()
boolean isInstance(Object obj)
T cast(Object obj)
<U> Class<? extends U> asSubclass(Class<U>
clazz)
Il
metodo seguente permette di sapere se il presente oggetto
Class è superclasse (o supertinterfaccia) dell'oggetto
passato come parametro:
boolean
isAssignableFrom(Class<?> cls)
I
seguenti metodi permettono l'ispezione delle annotazioni:
boolean
isAnnotationPresent(Class<? extends Annotation>
annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
<A extends Annotation> A getAnnotation(Class<A>
annotationClass)
Infine
una coppia di metodi che non hanno direttamente a che
vedere con la reflection: si tratta di metodi che permettono
di ottenere una file la cui posizione viene specificata,
attraverso una stringa, in relazione alla posizione
nel filesystem del presente oggetto class e alla sua
posizione nella gerarchia dei package. Per fare un esempio,
la stringa "/image/dog.gif" denota un'immagine
gif presente nella cartella image che si trova nella
package root della presente classe. Questi metodi sono
utili per recuperare immagini, file di configurazione
o altre risorse utili in fase di esecuzione:
URL
getResource(String name)
InputStream getResourceAsStream(String name)
Si
passa ora ad analizzare le classi, presenti nel package
java.lang.reflect, che completano il panorama degli
strumenti per la reflection.
Field
Il primo metodo che verrà illustrato permette
di conoscere, a partire da un oggetto Field, la classe
in cui è stato dichiarato:
Class<?>
getDeclaringClass()
Un
gruppo di metodi permette di conoscere il nome del campo,
il suo tipo, i suoi modificatori e l'eventuale tipo
generico:
String
getName()
Class<?> getType()
int getModifiers()
Type getGenericType()
A
partire da Java 1.5, un campo può rappresentare
anche la costante di una enum:
boolean
isEnumConstant()
Una
serie di metodi get() e set() permettono di ispezionare
il contenuto del presente oggetto Field o di modificarlo.
E' disponibile un metodo per ogni circostanza, sia per
trattare Object che per tipi primitivi:
Object
get(Object obj)
boolean getBoolean(Object obj)
byte getByte(Object obj)
char getChar(Object obj)
double getDouble(Object obj)
float getFloat(Object obj)
int getInt(Object obj)
long getLong(Object obj)
short getShort(Object obj)
void
set(Object obj, Object value)
void setBoolean(Object obj, boolean z)
void setByte(Object obj, byte b)
void setChar(Object obj, char c)
void setDouble(Object obj, double d)
void setFloat(Object obj, float f)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setShort(Object obj, short s)
Seguono
i consueti metodi che permettono di ispezionare le eventuali
annotazioni:
boolean
isAnnotationPresent(Class<? extends Annotation>
annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
<A extends Annotation> A getAnnotation(Class<A>
annotationClass)
Infine una coppia di metodi permette di ottenere una
rappresentazione sotto forma di stringa, con o senza
la dichiarazione di tipi parametrici:
String
toGenericString()
String toString()
Classe Constructor<T>
La classe Constructor<T> è una classe parametrica
al cui tipo T corrisponde a quello della classe di appartenenza.
Anche questa classe dispone di un metodo che permette
di ottenere la classe a cui il costruttore appartiene:
Class<T>
getDeclaringClass()
Segue
la descrizione dei metodi che permettono l'ispezione
del presente oggetto, in modo da conoscerne il nome,
i modificatori, il tipo dei parametri (semplici o generici),
le eventuali eccezioni generate (semplici o parametriche)
e i tipi parametrici dichiarati dal presente oggetto
Constructor<T>:
String
getName()
int getModifiers()
Class<?>[] getParameterTypes()
Type[] getGenericParameterTypes()
Class<?>[] getExceptionTypes()
Type[] getGenericExceptionTypes()
TypeVariable<Constructor<T>>[] getTypeParameters()
In
tutti i casi in cui i precedenti metodi restituiscono
un array, questo avrà lunghezza 0 se l'elemento
richiesto non è presente. Un apposito metodo
permette di sapere se il presente costruttore accetta
o meno un numero variabile di argomenti:
boolean
isVarArgs()
Una
serie di metodi permette di gestire le eventuali annotazioni.
Rispetto ai metodi già visti su Field, si noti
il metodo getParameterAnnotations(), che restituisce
un array di array che contiene le eventuali annotazioni
dei parametri formali, in ordine di dichiarazione:
boolean
isAnnotationPresent(Class<? extends Annotation>
annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
<A extends Annotation> A getAnnotation(Class<A>
annotationClass)
Annotation[][] getParameterAnnotations()
Il
metodo newInstance() permette di creare un oggetto di
tipo T a partire dal presente oggetto Constructor con
i parametri adeguati:
T
newInstance(Object... initargs)
L'ultimo
metodo segnalato permette di avere una rappresentazione
in formato stringa del costruttore rappresentato dal
presente oggetto, comprensivo di eventuali tipi parametrici:
String
toGenericString()
Classe
Method
I metodi della classe Method sono in gran parte comuni
a quelli della classe Constructor, con l'unica differenza
che un metodo prevede anche un tipo di ritorno, ragion
per cui la classe Method contiene un apposito metodo
getReturnType():
Class<?>
getDeclaringClass()
Class<?> getReturnType()
Type getGenericReturnType()
String getName()
int getModifiers()
Class<?>[] getParameterTypes()
Type[] getGenericParameterTypes()
Class<?>[] getExceptionTypes()
Type[] getGenericExceptionTypes()
TypeVariable<Method>[] getTypeParameters()
boolean isVarArgs()
Nel
caso il presente oggetto Method rappresenti il metodo
di un tipo di annotazione, è disponibile un apposito
metodo per ispezionare l'eventuale valore di default:
Object
getDefaultValue()
Seguono
i metodi per ispezionare le eventuali annotazioni, corrispondenti
in tutto e per tutto a quelli della classe Constructor<T>:
boolean
isAnnotationPresent(Class<? extends Annotation>
annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
<A extends Annotation> A getAnnotation(Class<A>
annotationClass)
Annotation[][] getParameterAnnotations()
Il
metodo toGenericString() permette di ottenere una rappresentazione
in formato stringa del metodo sottostante:
String
toGenericString()
Infine un metodo invoke() permette di invocare il presente
metodo su un oggetto specificato da un apposito parametro
con gli argomenti opportuni:
Object
invoke(Object obj, Object... args)
Classe
Array
La classe Array comprende solamente metodi statici,
che permettono di operare su oggetti Class di tipo array.
Il primo metodo che verrà illustrato permette
di conoscere la lunghezza di un array:
static
int getLength(Object array)
Seguono
una coppia di metodi che permettono di creare un array
a partire da un parametro di tipo Class che ne denota
il tipo e da un intero lenght che ne specifica la lunghezza.
La seconda versione del metodo newInstance() permette
di creare array di array, specificando l'insieme delle
lunghezze attraverso un array di interi:
static
Object newInstance(Class<?> componentType, int
length)
static Object newInstance(Class<?> componentType,
int[] dimensions)
Infine
una serie di metodi get() e set() permettono di leggere
o di impostare il valore di un elemento di un array,
specificandone il valore e l'indice:
static
Object get(Object array, int index)
static boolean getBoolean(Object array, int index)
static byte getByte(Object array, int index)
static char getChar(Object array, int index)
static double getDouble(Object array, int index)
static float getFloat(Object array, int index)
static int getInt(Object array, int index)
static long getLong(Object array, int index)
static short getShort(Object array, int index)
static
void set(Object array, int index, Object value)
static void setBoolean(Object array, int index, boolean
z)
static void setByte(Object array, int index, byte b)
static void setChar(Object array, int index, char c)
static void setDouble(Object array, int index, double
d)
static void setFloat(Object array, int index, float
f)
static void setInt(Object array, int index, int i)
static void setLong(Object array, int index, long l)
static void setShort(Object array, int index, short
s)
Interfaccia
Annotation
L'interfaccia Annotation viene implementata dalle classi
che rappresentano uno specifico tipo di annotazione.
L'unico metodo definito da questa interfaccia è
il seguente:
Class<?
extends Annotation> annotationType()
Questo
metodo restituisce l'oggetto Class che rappresenta l'annotazione
vera e propria, che può avere i propri modificatori,
le proprie annotazioni e i propri metodi, che a loro
volta possono avere un valore di default.
Classe Package
Il package è denotato da un nome, che segue la
dot notation caratteristica di Java (ad esempio java.io
o javax.swing);
String
getName()
I
seguenti metodi permettono di accedere ad una serie
di informazioni di carattere commerciale legate al venditore
del package, al titolo dell'implementazione, e alla
versione:
String getImplementationVendor()
String getImplementationTitle()
String getImplementationVersion()
Qualora
il package si rifaccia ad una specifica, è possibile
recuperare le informazioni corrispondenti al venditore
della specifica, al titolo e alla versione:
String
getSpecificationVendor()
String getSpecificationTitle()
String getSpecificationVersion()
Qualora
il presente package sia l'implementazione di una particolare
specifica, si può verificare l'eventuale compatibilità
con una versione di riferimento specificata dal parametro:
boolean
isCompatibleWith(String desired)
Seguono
i consueti metodi per l'ispezione delle eventuali annotazioni:
boolean
isAnnotationPresent(Class<? extends Annotation>
annotationClass)
<A extends Annotation> A getAnnotation(Class<A>
annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
Quindi
il caratteristico metodo toString():
String
toString()
Infine
una coppia di metodi statici che permettono di recuperare
un package a partire da un nome o l'elenco di tutti
i packages accessibili al ClassLoader:
static
Package getPackage(String name)
static Package[] getPackages()
Type
L'introduzione dei tipi parametrici in Java 5 ha richiesto
la definizione di un'apposita interfaccia Type, peraltro
vuota, e di quattro sue sottoclassi con delle forti
interazioni reciproche, come si vede in figura 2:
Figura 2 - Gerarchia di Type e delle sue sotto interfacce
Il
diagramma delle classi di Type è molto complesso,
e riflette la complessità della meta-rappresentazione
dei tipi parametrici.
GenericArrayType
Il linguaggio Java 5 permette di definire array di tipo
parametrico. Un apposito metodo permette di conoscere
il tipo generico dell'array stesso:
Type
getGenericComponentType()
ParametryzedType
L'interfaccia ParametryzedType contiene tutti i metodi
necessari ad ispezionare un tipo parametrico. Il primo
di questi permette di conoscere il tipo reale dei tipi
parametrici dichiarati:
Type[]
getActualTypeArguments()
A
questo proposito è bene ricordare che il tipo
parametrico reale è noto solamente a runtime,
un fattore che giustifica l'importanza del metodo precedente.
Il metodo seguente restituisce il tipo che racchiude
il presente tipo, permettendo in questo modo di ispezionare
i tipi parametrici nidificati:
Type
getOwnerType()
L'ultimo
dei metodi di questa interfaccia permette di conoscere
l'oggetto Type che rappresenta la classe o l'interfaccia
che ha dichiarato il presente tipo:
Type
getRawType()
A
questo proposito si ricorda che la classe Class implementa
a sua volta l'interfaccia Type.
TypeVariable <D extends GenericDeclaration>
L'interfaccia TypeVariable<D extends GenericDeclaration>
è un'interfaccia parametrica il cui tipo parametrico
formale D rappresenta un oggetto di tipo GenericDeclaration,
che verrà illustrato in seguito. Il primo metodo
di questa interfaccia restituisce un array di oggetti
Type che denota i limiti superiori dell'oggetto Type
corrente:
Type[]
getBounds()
Il
prossimo metodo restituisce il nome di questo tipo parametrico,
così come appare nel codice sorgente:
String
getName()
Infine
un metodo che restituisce un oggetto GenericDeclaration
che rappresenta la dichiarazione generica del presente
oggetto TypeVariable:
D
getGenericDeclaration()
L'interfaccia
GenericDeclaration contiene un solo metodo:
TypeVariable<?>[]
getTypeParameters()
Questo
metodo permette di ottenere un array di TypeVariable
che rappresentano le dichiarazioni di tipi parametrici
dichiarati dal presente oggetto GenericDeclaration in
ordine di dichiarazione.
WildcardType
L'interfaccia WildcardType denota il carattere jolly
'?' e le sue applicazioni all'interno delle dichiarazioni
di tipi parametrici (ad esempio <?>, <? extends
Number> o <? super JCompo-nent>) per specificare
limiti superiori o limiti inferiori:
Type[]
getLowerBounds()
Type[] getUpperBounds()
Conclusioni
Siamo finalmente giunti al termine di questa lunga carrellata
sulle classi che forniscono l'infrastruttura per la
reflection. Il prossimo mese sarà possibile vedere
un utilizzo pratico di tali classi per ispezionare oggetti
a runtime.
|