Java Reflection: esaminare e modificare un oggetto Java a runtime

In questo articolo vedremo come è possibile applicare la tecnica della riflessione (reflection) ad un linguaggio di programmazione come Java.

La reflection in informatica è la capacità di un programma di esaminare e/o modificare a runtime il proprio comportamento, e in particolare la struttura del proprio codice sorgente.

Un programma Java in esecuzione, per esempio, può esaminare le classi da cui è costituito, i nomi e le signature dei loro metodi, e così via. Il supporto per la reflection costituisce una delle più notevoli innovazioni di Java, che deriva da una tradizione di linguaggi (C, C++) in cui tutte le informazioni di tipo vengono consumate dal compilatore, al punto che il programma in esecuzione non ha neppure nozione di come la propria memoria sia suddivisa in variabili.

Le API Reflection sono raggruppata all’interno del package java.lang.reflect ma nessuna delle classi in esso contenute ha un costruttore pubblico (tranne la classe java.lang.reflect.ReflectPermission) , per poter utilizzare queste classi bisogna invocare degli appropriati metodi della classe java.lang.Class. Questa classe rappresenta l’entry point per tutte le operazioni di Reflection ed è possibile ottenerne una istanza da qualsiasi tipo di oggetto utilizzando il metodo getClass() (definito nella classe Object e quindi disponibile per tutti gli oggetti:

//un generico oggetto
Object o;
Class c = o.getClass();
//oppure da una stringa
Class c = "AppuntiSoftware".getClass();

Esiste un altro metodo che possiamo invocare per ottenere una istanza di Class, il metodo forName():

// posso ottenere un riferimento all'oggetto Class di tipo Stringa in due modiString test = new String();
Class c1 = test.getClass();
//oppure
Class c2 = Class.forName("java.lang.String");

Dunque utilizziamo il metodo getClass() quando abbiamo l’istanza dell’oggetto su cui indagare, altrimenti se sappiamo il nome completo della classe (il Canonical Name che è formato dal nome della classe più quello del suo package) utilizziamo forName().

Per quanto riguarda i tipi primitivi esiste una sintassi diversa che prevede l’uso del metodo .class:

c = int.class;          // uguale a Integer.TYPE
c = String.class;       // uguale a "AppuntiSoftware".getClass()
c = byte[].class;       // un array di byte
c = Class[][].class;    // una matrice di Class Object

Ottenuto un oggetto di tipo Class possiamo iniziare ad utilizzare la Reflection API:  possiamo ad esempio investigare su quali siano le superclassi di una data classe utilizzando il metodo getSuperClass(), nell’esempio seguente viene stampata a video tutta la gerarchia di classi della classe PrintStream:

Class c = java.io.PrintStream.class;
if (!c.isPrimitive()) {
for(Class s = c; s != null; s = s.getSuperclass()) {
if("java.lang.Object".equals(s.getName()))
System.out.println(s.getName());
else
System.out.println(s.getName() + " estende");
}
}

il metodo isPrimitive() serve per vedere se un oggetto è un valore primitivo, esistono ovviamente dei metodi di “test” per verificare se un oggetto è un array, una classe locale, ecc..:

boolean isPrimitive()
boolean isArray()
boolean isLocalClass()
boolean isAnonymousClass()
boolean isMemberClass()
boolean isEnum()
boolean isAnnotation()
boolean isInterface()

Se abbiamo un array di elementi possiamo conoscere il tipo degli elementi utilizzando getComponentType():

String array [] = new String[3];
array[0] ="ciao";
array[1]="mondo";
array[2]="!";
Class c = array.getClass();
if (c.isArray())
System.out.println(c.getComponentType());

Vi sono poi dei metodi per sapere quali sono i campi, i costruttori, i metodi ed eventuali classi e/o interfacce membri di una classe:

Field[] getFields()
Constructor[] getConstructors()
Method[] getMethods()
Class[] getClasses()

questi metodi ritornano gli elementi pubblici non solo della classe presa in esame ma anche delle sue superclassi, per visionare tutti gli elementi (non solo quelli pubblici ma anche privati) della sola classe in esame si utilizzano invece i seguenti metodi:

Field[] getDeclaredFields()
Constructor[] getDeclaredConstructors()
Method[] getDeclaredMethods()
Class[] getDeclaredClasses()

Proviamo a stampare a video tutti i costruttori della classe String ed i tipi di parametri che essi accettano:

String str = new String("AppuntiSoftware");
Class clazz = str.getClass();
Constructor costruttori[] = clazz.getConstructors();
System.out.println ("Numero Costruttori: " + costruttori.length);
for (int i=0; i < costruttori.length; i++)
{
System.out.println ("Costruttore n." + (i+1) + ": " + costruttori[i].toGenericString());
Class  tipiParam[] = costruttori[i].getParameterTypes();
if (tipiParam.length == 0)
{
System.out.println ("Questo costruttore non ha Parametri!");
}else{
System.out.println ("Parametri: ");
for (int j=0; j < tipiParam.length; j++)
System.out.println (tipiParam[j].getName());
}
}

l’unica cosa da notare è il valore restituito dal metodo getName(): esso restituisce il nome di una classe completo (incluso il package) se essa è diversa da un array altrimenti restituisce una stringa col tipo di dati contenuti nell’array preceduti da un simbolo “[” (tanti quanti sono le dimensioni dell’array) , ad esempio un array di Stringhe ritorna un valore uguale a “[Ljava.lang.String”. Se però si tratta di un array di tipi primitivi viene ritornata una codifica in base al tipo degli elementi:

Element Type Encoding
boolean Z
byte B
char C
classe o interfaccia Lnome della classe
double D
float F
int I
long J
short S

ad esempio new int[3][4].getClass().getName() ritorna questa Stringa: “[[I”.

Proviamo ora ad invocare un metodo della classe String:

try {
String str = new String("Ciao da ");
Class clazz = str.getClass();
Method metodo= clazz.getMethod("concat", new Class[]{new String().getClass()});
System.out.println ( metodo.invoke(str, new String("AppuntiSoftware")) );
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}

come parametri di getMethod() vanno passati il nome del metodo da invocare ed un array di Class object che identificano il tipo di parametri da passare e in quale ordine passarli (nel nostro caso ne è uno di tipo Stringa), fatto ciò è possibile richiamare il metodo invoke() specificando come primo parametro l’oggetto su cui fare la chiamata e come secondo parametro la lista dei parametri da passare al metodo.

1 Stella2 Stelle3 Stelle4 Stelle5 Stelle (2 voti, media: 5,00 di 5)
Loading...
You can leave a response, or trackback from your own site.

Leave a Reply

*