Java Reflection

In questo articolo affrontiamo il concetto di reflection (riflessione) in Java

Un programma si dice riflessivo se è in grado di analizzare la funzionalità delle classi. In Java, la riflessione offre un ricco e sofisticato set di strumenti per scrivere programmi che elaborano dinamicamente codice Java. Le operazioni più importanti sono:

  • Analisi delle funzionalità delle classi in runtime.
  • Ispezione degli oggetti in runtime, ad esempio scrivere un solo metodo toString che funzioni per tutte le classi.
  • Implementazione di codice di elaborazione di un array generico.

Di seguito possiamo vedere le classi principali delle Reflection API

Reflection API

Reflection API

La classe Class

Quando un programma è in esecuzione, il sistema runtime di Java mantiene in memoria l’identificazione per tutti gli oggetti. In questa informazione abbiamo sostanzialmente la classe di appartenenza di ciascun oggetto. L’informazione di tipo in runtime è utilizzata dalla macchina virtuale per selezionare il metodo corretto da eseguire. Possiamo accedere a tale informazione tramite la classe speciale Class . Con il metodo getClass() della classe Object otteniamo una istanza di tipo Class:

Un oggetto Class descrive le proprietà di una determinata classe. Uno dei metodi più utilizzati della classe Class è getName, che restituisce il nome della classe. Ad esempio:

Quando il nome di una classe è memorizzato sotto forma di stringa, possiamo ottenere un oggetto di tipo Class fornendo al metodo statico forName la stringa contenente il nome della classe desiderata:

Un altro modo per ottenere un oggetto di tipo Class prevede l’utilizzo diretto di Class. Cioè dato un qualsiasi tipo, possiamo ottenere da questo tipo l’oggetto della classe con .class:

La macchina virtuale gestisce un oggetto Class unico per ogni tipo, quindi è possibile utilizzare l’operatore == per confrontare gli oggetti della classe, come di seguito:

Per creare una istanza di una classe è possibile usare il metodo newIstance() di Class. Questo metodo crea una nuova istanza dello stesso tipo della classe attraverso il quale viene chiamato. Il metodo newIstance chiama il costruttore predefinito per dar via alla creazione dell’oggetto. Quindi a questo punto tramite i metodi forName e newIstance siamo in grado di creare un oggetto con il nome di una classe memorizzato in una stringa:

Analisi delle funzionalità delle classi

La reflection consente l’analisi delle funzionalità di una classe. Nel package java.lang.reflect abbiamo le seguenti classi:

  • Field : descrive i campi,
  • Method : descrive i metodi,
  • Constructor : descrive i costruttori.

 

Queste classi hanno un metodo getName che restituisce il nome della voce indicata. La classe Field ha il metodo getType che restituisce un oggetto di tipo Class che descrive il tipo del campo, mentre le classi Method e Constructor hanno metodi che riportano i tipi dei parametri; la classe Method ha anche un metodo getModifiers che restituisce un intero con diversi bit di stato on oppure off, e che descrive i modificatori utilizzati, per esempio public e static.

Quindi possiamo utilizzare i metodi statici della classe Modifier per effettuare l’analisi del valore intero restituito da getModifiers. I metodi isPublic, isPrivate o isFinal della classe Modifier consentono di stabilire se un metodo oppure un costruttore è public private o final. E’ sufficiente invocare il metodo appropriato della classe Modifier sull’intero restituito da getModifiers.

Si può anche utilizzare il metodo Modifier.toString per visualizzare i modificatori. I metodi getFields, getMethods e getConstructors della classe Class restituiscono array dei campi, metodi e costruttori pubblici supportati dalla classe, inclusi i membri pubblici della superclasse.

I metodi getDeclareFields, getDeclareMethods e getDeclareConstructors della classe Class restituiscono invece array costituiti da tutti i campi, operazioni e costrutti dichiarati nella classe; ciò include i membri privati e protetti ma non i membri della superclasse.

Vediamo ora un esempio che mostra come visualizzare tutte le informazioni relative ad una classe. Il programma richiede il nome di una classe e poi visualizza le firme di tutti i metodi e dei costruttori, oltre ai nomi di tutti i campi dati della classe indicata.

Ad esempio se inseriamo la classe java.lang.Double, il programma restituisce il seguente output

Analisi degli oggetti a runtime

Con la riflessione è possibile analizzare il contenuto di un campo di oggetti che non si conoscono, direttamente in fase di compilazione. Il metodo che consente ciò è il get della classe Field . Se per esempio field è un oggetto di tipo Field, ottenuto dal metodo getDeclaredFields, e obj è un oggetto della classe di cui field è un campo, allora field.get(obj) ritorna un oggetto il cui valore è proprio quello corrente del campo di obj.
Vediamo a questo punto un esempio per chiarirci le idee su quanto appena detto:

E’ importante notare che il metodo get è utilizzabile solo per conoscere campi accessibili. Se un programma Java non dispone di un gestore di sicurezza particolari è possibile utilizzare il metodo setAccessible(true) per scavalcare tali controlli.

Oltre all’analisi, è possibile anche effettuare modifiche sui campi in esame. Tale operazione si effettua tramite il metodo field.set(obj, value) che imposta con il nuovo valore il campo rappresentato da field dell’oggetto obj.

Adesso vediamo come scrivere un metodo toString generico che funziona con qualsiasi classe. Questo metodo utilizza getDeclaredFields per acquisire tutti i campi dati, poi invoca il metodo setAccessible per rendere accessibili ogni campo. Per ogni campo si acquisiscono il nome e il valore corrispondente.

Reflection applicate ad un array

Vediamo ora come risolvere un problema assai tipico quando si utilizzano array. Spesso succede di avere un array pieno e lo si vorrebbe far crescere tramite un metodo generico capace di aumentarne la capacità.

Per definire tale metodo, utilizziamo i metodi messi a disposizione dalla classe Array presente nel package java.lang.reflect. In particolare usiamo il metodo statico newIstance che costruisce un nuovo array:

dove componentType è il tipo di componente del nuovo array e newlength è la nuova lunghezza. Per recuperare la lunghezza dell’array precedente basta utilizzare il metodo Array.getLength(a). Invece per recuperare il tipo di componente del nuovo array occorre:

  1. acquisire l’oggetto classe di a
  2. verificare che sia un array
  3. determinarne il tipo con il metodo getComponentType di Class

Per cui il metodo arrayGrow() è il seguente:

Con questo metodo è possibile aumentare la dimensione di qualsiasi array. Di seguito possiamo vedere un esempio completo del suo utilizzo:

Conclusioni

In questo articolo è stata fatta una panoramica sulla Reflection API, che permette di manipolare in modo dinamico gli oggetti Java. Per maggiori informazioni a riguardo potete consultare il seguente indirizzo:
http://java.sun.com/developer/technicalArticles/ALT/Reflection/

Bibliografia

“Core Java 2 Vol.1 Fondamenti”, Cay S.Horstmann-Gary Cornell
Manuale Pratico di Java II Edizione

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *