Creazione
di oggetti
LAPI Reflection permette di creare oggetti anche
nel caso la classe base non sia conosciuta nel momento
in cui il programma viene scritto. Il sistema più
rapido per creare un oggetto a partire da una classe è
il metodo newInstance() di Class:
public
T newInstance()
Questo
metodo restituisce un oggetto di tipo T (il tipo base
della classe stessa) creato invocando il costruttore
privo di parametri. Se la classe corrispondente non
possiede un costruttore vuoto, viene generata una a
NoSuchMethodException. Il seguente frammento di codice
mostra come caricare un oggetto Class di tipo java.lang.String
e crearne unistanza:
try
{
Class<String> c = (Class<String>)ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
String s = c.newInstance();
}
catch (Exception e) {
e.printStackTrace();
}
Per
creare oggetti attraverso un costruttore dotato di parametri,
è necessario operare su un oggetto di tipo Constructor.
I passaggi necessari per completare loperazione
sono tre:
- Prendere
un oggetto Class relativo al tipo di oggetti che si
desidera creare
- Creare
un oggetto Constructor attraverso linvocazione
dellapposito metodo getConstructor() sulloggetto
Class. Il metodo getConstructor() richede come parametro
un array di Class che deve corrispondere ai tipi richiesti
dal costruttore desiderato
- Creare
loggetto desiderato attraverso linvocazione
del metodo newInstance() sulloggetto Constructor
appena ottenuto. Il metodo newInstance() richiede
come parametro un array di Object che deve contenere
i parametri da passare al costruttore.
Si
osservi che i parametri appartenenti a tipi primitivi
devono essere specificati attraverso le classi dei corrispondenti
Wrapper Type (Integer, Float, Boolean ecc). Un esempio
dovrebbe chiarire la situazione. Per replicare con la
reflection la seguente istruzione:
Vector
v = new Vector(10,5);
È
necessario utilizzare il seguente frammento di codice:
try
{
Class<Vector> c = (Class<Vector>)ClassLoader.getSystemClassLoader()
.loadClass("java.util.Vector");
Constructor<Vector> constructor =
c.getConstructor(new Class[] { Integer.class,
Integer.class
});
Vector v = constructor.newInstance(new Object[]
{ new Integer(10),
new
Integer(5) });
}
catch (Exception e) {
e.printStackTrace();
}
La
prima riga carica la classe java.util.Vector
dal ClassLoader; la seconda recupera il costruttore
che opera su due parametri interi; la terza invoca il
costruttore sulla coppia di parametri 10 e 5, racchiusi
negli appositi wrapper type.
Unapplicazione
che debba realmente ricorrere alla reflection per creare
oggetti a runtime deve verosimilmente mostrare un elenco
di costruttori e permettere allutente di scegliere
quello da invocare. Linterrogazione di una classe
al fine di ottenere lelenco di costruttori, metodi
e attributi è stata illustrata nellarticolo
del mese scorso.
Lettura
e scrittura di attributi
La Reflection permette laccesso dinamico agli
attributi di un oggetto: in sostanza è possibile
interrogare un oggetto senza conoscerne a priori la
classe di appartenenza. Anche in questo caso si tratta
di unoperazione caratterizzata da tre passaggi:
Creare un oggetto Class relativo alla classe delloggetto
da esaminare
Creare un oggetto Field corrispondente al campo da esaminare
con il metodo getField(String) di Class
Recuperare il valore del campo con il metodo get(Object),
che richede come parametro listanza da interrogare.
La
classe Field dispone anche di metodi specializzati per
recuperare valori appartenenti a tipi primitivi (getInt(),
getFloat() ecc). Questi ultimi sono metodi di convenienza:
è sempre possibile recuperare valori primitivi
incapsulati negli appositi oggetti Wrapper con il consueto
metodo get(Object). Si noti tuttavia che, qualora lattributo
contenga un array di primitivi, il valore restituito
sarà un array di primitivi, non un array di wrapper
type. Questo argomento verrà chiarito ulteriormente
nel paragrafo sulla manipolazione di array.
Nel
caso si desideri leggere un attributo statico, largomento
della get(Object) deve essere null. Il seguente frammento
di codice mostra come fare per leggere lattributo
statico PI (pigreco) dalla classe java.lang.Math:
try
{
Class<java.lang.Math> c = (Class<java.lang.Math>)ClassLoader.getSystemClassLoader()
.loadClass("java.lang.Math");
Field f = c.getField("PI");
System.out.println(f.get(null));
}
catch (Exception e) {
e.printStackTrace();
}
In
maniera del tutto speculare esiste un metodo che permette
di modificare il valore degli attributi di un determinato
oggetto:
void
set(Object obj, Object value)
il
metodo set() richede come parametri loggetto su
cui effettuare loperazione e il valore da impostare.
Anche in questo caso esiste una famiglia di metodi di
comodo per manipolare valori primitivi (setInteger(Object,int),
setFloat(Object,float) e così via).
Invocazione
di metodi
La reflection permette anche di invocare metodi in modo
dinamico. I passaggi necessari sono simili a quelli
già visti nel caso dei costruttori:
- Prendere
un oggetto Class relativo alla famiglia di oggetti
che si desidera creare
- Creare
un oggetto Method attraverso linvocazione dellapposito
metodo getMethod(String m,Class[] parameterTypes)
sulloggetto Class. Questo metodo richede come
parametri il nome del metodo e un array di Class che
deve corrispondere ai tipi richiesti dal metodo desiderato.
Se si desidera ottenere un metodo privo di parametri,
occorre passare un vettore di lunghezza zero o null.
- Invocare
il metodo attraverso la invoke(Object o,Object[] args)
a cui bisogna passare loggetto su cui si vuole
eseguire loperazione e il vettore degli argomenti
(null o un vettore di lunghezza zero se si desidera
invocare un metodo privo di parametri). La invoke()
restituisce sotto forma di Object il valore di ritorno
della chiamata. Nel caso si tratti di un valore primitivo,
questo sarà contenuto allinterno di un
oggetto wrapper.
Nellesempio
seguente viene creato un oggetto Vector con alcune stringhe
al suo interno; quindi viene invocato il metodo elements
con la reflection. La chiamata non richiede parametri,
pertanto sia la getMethod() che la invoke() vengono
chiamate con un vettore di lunghezza zero. Il valore
di ritorno viene convertito in una Enumeration grazie
alloperatore di casting, e quindi viene eseguita
una normale operazione di iterazione tra gli elementi
in esso contenuti:
Vector
v = new Vector();
v.add("primo");
v.add("secondo");
v.add("terzo");
try {
Class c = v.getClass();
Method
m = c.getMethod("elements",new Class[0]);
Enumeration
elements = (Enumeration)m.invoke(v,new Object[0]);
while(elements.hasMoreElements())
System.out.println(elements.nextElement());
}
catch
(Exception e) {
e.printStackTrace();
}
Manipolazione
di array
La classe Array, presente nel package java.lang.reflect,
dispone di una serie di metodi statici che permettono
di manipolare array. Per creare un array sono disponibili
due versioni del metodo newInstance:
static
Object newInstance(Class<?> componentType, int
length)
static Object newInstance(Class<?> componentType,
int[] dimensions)
Il
primo metodo permette di creare array monodimensionali
specificando il tipo e la lunghezza; il secondo restituisce
array multidimensionali sulla base di un vettore di
interi in cui ciascun elemento denota una dimensione.
In pratica listruzione
int[]
array = (int[])Array.newInstance(int.class,5)
è
equivalente ad una
int[]
array = new int[5];
mentre
listruzione
int[][]
array = (int[][])Array.newInstance(int.class,new int[]{3,4});
equivale
alla direttiva
int[][]
array = new int[3][4]
Nel
definire array è necessario sottolineare che
i vettori di tipi primitivi sono diversi da vettori
di wrapper type. Si osservino i due esempi seguenti:
int[]
array1 = (int[])Array.newInstance(int.class,5);
Integer[] array2 = (Integer[])Array.newInstance(Integer.class,5);
Il
primo crea un vettore di int, il secondo un vettore
di Integer. Si tratta di due tipi differenti, come del
resto viene sottolineato dal primo parametro della chiamata.
In nessun caso è possibile convertire luno
nellaltro con unoperazione di casting: lunico
modo per ottenere un vettore di int a partire da un
vettore di Integer (o viceversa) è quello di
creare un nuovo vettore e copiare gli elementi uno ad
uno, effettuando lopportuna correzione su ogni
elemento.
La
classe Array dispone di una serie di metodi, già
illustrati nellarticolo di novembre, per interrogare
o modificare gli elementi di un array. Il seguente frammento
di codice definisce un metodo che, preso in input un
vettore qualsiasi, ne restituisce uno dello stesso tipo
ma di lunghezza doppia, che contiene nelle prime posizioni
tutti gli elementi del vettore in ingresso. Questa breve
procedura mostra tutte le modalità di interazione
con gli array tramite la reflection:
public
static Object raddoppiaArray(Object source) {
if(!source.getClass().isArray())
throw
new IllegalArgumentException("Il parametro deve
essere un array");
int
sourceLength = Array.getLength(source);
Class
componentClass = source.getClass().getComponentType();
Object
result = Array.newInstance(componentClass, sourceLength
* 2);
for(int
i=0;i<sourceLength;i++)
Array.set(result,i,Array.get(source,i));
return
result;
}
La
prima istruzione verifica che il parametro source sia
un array, e in caso contrario genera uneccezione.
Quindi larray viene interrogato per con il metodo
Array.getLength(). Listruzione successiva mostra
come ottenere il tipo degli elementi contenuti nellarray,
che è diverso dal tipo dellarray (ad esempio
se il primo è int, il secondo sarà int[]).
Quindi viene creato un vettore con il metodo Array.newInstance().
Infine attraverso un ciclo, gli elementi dellarray
source vengono copiati nellarray result.
Conclusioni
Questo mese abbiamo completato la panoramica sullAPI
reflection, che permette di manipolare in modo dinamico
gli oggetti Java. Per prima cosa abbiamo visto come
creare istanze di oggetti, quindi come si leggono e
scrivono gli attributi e come si invocano i metodi.
Infine abbiamo illustrato come si creano e manipolano
gli Array. Queste conoscenze, unite a quelle presenti
nei due articoli precedenti, permettono di realizzare
procedure che operano su oggetti Java anche nei casi
in cui la classe base non è nota al momento dello
sviluppo.
|