La riflessione (o introspezione) è una caratteristica di Java che permette ai programmi di investigare a tempo di esecuzione sui tipi effettivi degli oggetti manipolati.
Il cardine della riflessione è rappresentato dalla classe Class.
Ciascun oggetto di classe Class rappresenta una delle classi del programma
Pur non essendo classi, anche i tipi primitivi hanno un corrispondente oggetto di tipo Class
La classe Class ha un parametro di tipo, che chiameremo “T”.
Come un serpente che, apparentemente, si morde la coda, il parametro di tipo di un oggetto Class indica il tipo che questo oggetto rappresenta. Ad esempio, se x è l’oggetto Class relativo alla classe Employee, il tipo di x è Class <Employee>.
Nelle prossime slide, vedremo come il parametro di tipo viene utilizzato dai metodi di Class.
Esaminiamo alcuni metodi della classe Class<T>:
public String getName()
restituisce il nome di questa classe, completo di eventuali nomi di pacchetti.
public T newInstance()
crea e restituisce un nuovo oggetto di questa classe, invocando un costruttore senza argomenti, che questa classe deve possedere.
public static Class<?> forName(String name)
restituisce l’oggetto Class corrispondente alla classe di nome “name”; la stringa “name” deve contenere anche l’indicazione degli eventuali pacchetti cui la classe appartiene.
public Class<? super T> getSuperclass()
restituisce l’oggetto Class corrispondente alla superclasse diretta di questa; se questa è Object, oppure è un’interfaccia o un tipo base, il metodo restituisce null.
public boolean isInstance(Object x)
restituisce vero se (e solo se) il tipo effettivo di x è sottotipo di questa classe.
Per ottenere un riferimento ad un oggetto di tipo Class, è possibile utilizzare uno dei seguenti tre metodi.
Primo metodo:
Nella classe Object, è presente il metodo public Class
< ? extends Object > getClass()
Questo metodo restituisce l’oggetto Class corrispondente al tipo effettivo di questo oggetto.
Il tipo restituito di getClass merita attenzione.
Supponiamo di applicare getClass ad un oggetto di tipo dichiarato Employee:
Employee e = ...
??? = e.getClass();
A che tipo di variabile ci aspettiamo di poter assegnare il risultato della chiamata?
A prima vista, la risposta sembrerebbe Class < Employee >
A pensarci meglio, se il tipo effettivo di “e” fosse Manager, l’oggetto restituito da getClass sarebbe di tipo Class<Manager>, che non è assegnabile a Class < Employee >!
Pertanto, il tipo più appropriato (cioè, più specifico) a cui possiamo assegnare il risultato è Class <? extends Employee >
Quindi, il tipo restituito di getClass dovrebbe esprimere il seguente concetto:
applicato ad una classe A (che lo eredita da Object), questo metodo restituisce un oggetto di tipo Class < ? extends A >
Purtroppo, non è possibile esprimere in Java questo concetto di tipo restituito.
Il type-checker tratta il metodo getClass in modo particolare, simulando il tipo restituito che il linguaggio non è in grado di esprimere.
Lo strano tipo restituito ricorda al programmatore che questo metodo è soggetto ad un trattamento speciale da parte del compilatore.
Normalmente, il parametro di tipo “? extends Object” non ha senso, perché non limita in alcun modo il parametro jolly.
Secondo metodo:
Il secondo metodo per ottenere un riferimento ad un oggetto di tipo Class è rappresentato dall’operatore “.class”
Tale operatore si applica al nome di una classe o di un tipo base, come in:
Employee.class
String.class
java.util.LinkedList.class
int.class
Questo operatore ha carattere statico, nel senso che il suo valore è noto al momento della compilazione.
Terzo metodo:
Il terzo metodo è rappresentato dal metodo statico forName della classe Class, che abbiamo già presentato in questa lezione.
Questa tecnica ha carattere dinamico, in quanto il valore restituito da forName non è noto al momento della compilazione.
Data la dichiarazione:
Employee e = new Manager(...);
Per ognuna delle seguenti espressioni, dire se è corretta o meno, e in caso affermativo calcolarne il valore
e instanceof Employee
e instanceof Manager
e.class instanceof Employee
e.getClass() == Manager
e.getClass() == Employee.class
e.getClass() == "Manager"
e.getClass() == Manager.class
e.getClass().equals(Manager.class)
I metodi della classe Class permettono di ricavare numerose informazioni sulla classe in questione. In particolare, è possibile conoscere l’elenco di tutti i campi, metodi e costruttori appartenenti alla classe. A tale scopo, esistono le classi Field, Method e Constructor, che rappresentano gli elementi omonimi di una classe.
Per ottenere tali informazioni, sono utili i seguenti metodi di Class:
public Field[] getFields()
public Field[] getDeclaredFields()
public Method[] getMethods()
public Method[] getDeclaredMethods()
public Constructor[] getConstructors()
public Constructor[] getDeclaredConstructors()
Il metodo getFields restituisce tutti i campi pubblici di questa classe, anche ereditati
Il metodo getDeclaredFields restituisce tutti i campi dichiarati in questa classe (e non nelle superclassi)
La classe Field rappresenta un campo di una classe.
Essa dispone di metodi per leggere e modificare il contenuto di un campo, conoscere il suo nome e il suo tipo.
In particolare, abbiamo: (vedi figura).
Alcuni metodi sollevano l’eccezione verificata IllegalAccessException, quando si tenta di accedere ad un campo che non è accessibile a causa della sua visibilità.
La classe Method rappresenta un metodo di una classe.
Essa dispone di metodi per conoscere il nome del metodo, il numero e tipo dei parametri formali e il tipo di ritorno.
Inoltre, è possibile invocare il metodo stesso.
In particolare, abbiamo: (vedi figura).
La sintassi “Object…args” indica che invoke accetta un numero variabile di argomenti.
Dalla versione 1.5, Java prevede un meccanismo per dichiarare metodi con un numero variabile di argomenti (metodi variadici, o, in breve, varargs).
Se T è un tipo di dati, con la scrittura
f(T … x)
si indica che f accetta un numero variabile di argomenti (anche zero), tutti di tipo T.
I puntini sospensivi devono essere necessariamente tre.
Gli argomenti possono essere passati separatamente, come in f(x1, x2, x3), oppure tramite un array, come in f(new T[] {x1, x2, x3}).
All’interno del metodo f, si può accedere agli argomenti utilizzando x come un array di tipo T.
Ogni metodo può avere un solo argomento variadico, che deve essere l’ultimo della lista, come il metodo invoke della classe Method, illustrato nella slide precedente
public Object invoke(Object x, Object...args)
1) Implementare un metodo, chiamato reset, che prende come argomento un oggetto ed imposta a zero tutti i suoi campi interi pubblici.
2) Implementare un metodo che, dato un oggetto, parte dalla classe che rappresenta il tipo effettivo dell’oggetto e ne restituisce la superclasse più generale, escludendo Object (quindi, la penultima classe, prima di arrivare a Object).
4. Risoluzione dell'overloading e dell'overriding
5. Controllo di uguaglianza tra oggetti
6. Classi interne, locali ed anonime
7. Iteratori, teoria e pratica
8. Clonazione di oggetti. Confronto tra oggetti.
9. Elementi di programmazione di interfacce grafiche
10. Il paradigma Model-View-Controller. Il pattern Strategy
11. I pattern Composite e Decorator
12. I pattern Template Method e Factory Method
13. Classi e metodi parametrici
14. La libreria Java Collection Framework: le interfacce Iterable, ...
15. La libreria Java Collection Framework: la classe HashSet e le l...
16. Parametri di tipo con limiti
17. L'implementazione della programmazione generica: la cancellazio...
18. La riflessione
19. Introduzione al multi-threading
22. Classi enumerate