Come
ispezionare le classi
Per ogni classe, la JVM mantiene un oggetto Class che
contiene tutte le informazioni sulla classe stessa in
termini di composizione. Il mese scorso sono state analizzate
le API reflection, e si è visto come a partire
da un oggetto Class sia possibile ottenere lelenco
di campi, metodi, costruttori e così via, laddove
ciascun elemento del linguaggio è a sua volta
rappresentato da unapposita classe (Field, Constructor,
Method e così via). Ciascuno di questi oggetti
può a sua volta fornire un insieme completo di
informazioni sullelemento sottostante. E
giunto il momento di analizzare codice funzionante che
esegua le operazioni descritte fino ad ora.
Come
recuperare gli oggetti Class
Esistono tre sistemi per recuperare oggetti Class. Il
primo è attraverso il metodo statico forName()
presente nella classe Class stessa. In questo caso lunico
parametro da fornire è il nome della classe sotto
forma di stringa, uninformazione che può
essere fornita tranquillamente a runtime.
import
java.io.*;
public
class ClassBuster1 {
public static void main(String argv[]) {
try {
BufferedReader lineReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Inserire il nome completo di
una classe: ");
String className = lineReader.readLine();
Class c = Class.forName(className);
System.out.println("La classe cercata e': "
+ c);
}
catch (ClassNotFoundException e) {
System.out.println("La classe richiesta non e'
stata trovata");
e.printStackTrace();
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Lanciando
questo programma viene richiesto allutente di
inserire il nome di una classe. Quindi il programma
cerca la classe inserita e se la trova stampa informazioni
sulla classe trovata e sulle sue superclassi:
C:\>java
ClassBuster1
Inserire il nome completo di una classe:
javax.swing.JTable
La classe cercata e': class javax.swing.JTable
La superclasse numero 1 e': class javax.swing.JTable
La superclasse numero 2 e': class javax.swing.JComponent
La superclasse numero 3 e': class java.awt.Container
La superclasse numero 4 e': class java.awt.Component
La superclasse numero 5 e': class java.lang.Object
Se
la classe non viene trovata, viene stampato a schermo
un messaggio di errore e lo stack del programma al momento
dellesecuzione.
Se
si dispone di unistanza di un generico oggetto
Object, è possibile chiamare lapposito
metodo getClass():
public
static Class getClass(Object o) {
return o.getClass();
}
Infine,
se si conosce il nome di una classe al momento della
realizzazione del programma, è possibile recuperare
il relativo oggetto Class aggiungendo il suffisso .class
al suo nome:
Class
c = java.util.Vector.class;
Distinguere
la natura delloggetto Class
La classe Class dispone di un buon numero di metodi
che permettono di riconoscere la natura delloggetto
Class stesso. Il prossimo esempio è una variante
del programma precedente, che dopo aver chiesto il nome
di una classe, cerca di verificare se si tratta di uninterfaccia,
di una classe interna o di una classe:
import
java.io.*;
public
class ClassBuster2 {
public static void main(String argv[]) {
try {
BufferedReader lineReader
= new BufferedReader(new InputStreamReader(System.in));
System.out.println("Inserire
il nome completo di una classe: ");
String className
= lineReader.readLine();
Class c = Class.forName(className);
if(c.isInterface())
System.out.println("L'interfaccia
cercata e': " + c);
else if(c.isMemberClass())
System.out.println("La
classe interna cercata e': " + c);
else
System.out.println("La
classe cercata e': " + c);
}
catch (ClassNotFoundException
e) {
System.out.println("La
classe richiesta non e' stata trovata");
e.printStackTrace();
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Si
provi ad inserire il nome di una classe, di uninterfaccia
come java.util.List, o di una classe interna come javax.swing.JTable$PrintMode.
Esistono metodi per verificare se loggetto Class
rappresenta un array, una enum o altre cose, ma non
è stato possibile inserirle nellesempio
precedente: il loro funzionamento comunque dovrebbe
oramai risultare chiaro.
Identificare
i modificatori
Una feature importante quando si usa la reflection è
la capacità di identificare i modificatori. Come
già spiegato nel precedente articolo, i modificatori
di classi, metodi, campi e costruttori vengono riportati
sotto forma di un intero che deve poi essere processato
con lapposito oggetto java.lang.reflect.Modifier.
Nel seguente esempio vedremo come
import
java.io.*;
import java.lang.reflect.*;
public
class ClassBuster3 {
public static void main(String argv[]) {
try {
BufferedReader lineReader
= new BufferedReader(new InputStreamReader(System.in));
System.out.println("Inserire
il nome completo di una classe: ");
String className
= lineReader.readLine();
Class c = Class.forName(className);
System.out.println("L'oggetto
Class ha i seguenti modificatori:
"+Modifier.toString(c.getModifiers()));
}
catch (ClassNotFoundException
e) {
System.out.println("La
classe richiesta non e' stata trovata");
e.printStackTrace();
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Un
esempio di utilizzo della classe precedente:
C:\>java
ClassBuster3
Inserire il nome completo di una classe:
java.util.AbstractList
L'oggetto Class ha i seguenti modificatori: public abstract
Si
provi ad usare il programma anche su classi differenti,
come java.lang.String, che è public e final.
Il meccanismo appena individuato opera altrettanto bene
su qualunque altro elemento di una classe, come sarà
possibile vedere più avanti.
Identificare le interfacce implementate da una classe
Si è già visto nel primo esempio come
sia possibile scoprire la superclasse di un oggetto
Class. Scoprire le sue interfacce è altrettanto
semplice: basta usare lapposito metodo getInterfaces(),
come si può vedere nel prossimo esempio:
import
java.io.*;
import java.lang.reflect.*;
public
class ClassBuster4 {
public static void main(String argv[]) {
try {
BufferedReader lineReader
= new BufferedReader(new InputStreamReader(System.in));
System.out.println("Inserire
il nome completo di una classe: ");
String className
= lineReader.readLine();
Class c = Class.forName(className);
Class interfaces[]
= c.getInterfaces();
System.out.printf("La
classe %s implementa le seguenti interfacce:%n",c);
for(Class i : interfaces)
System.out.printf("%s
%s%n",Modifier.toString(i.getModifiers()),i);
}
catch (ClassNotFoundException
e) {
System.out.println("La
classe richiesta non e' stata trovata");
e.printStackTrace();
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Si
provi ad eseguire il precedente programma su una classe
che implementa un gran numero di interfacce, come la
classe java.util.Vector:
C:\>java
ClassBuster4
Inserire il nome completo di una classe:
public abstract interface interface java.util.List
public abstract interface interface java.util.RandomAccess
public abstract interface interface java.lang.Cloneable
public abstract interface interface java.io.Serializable
Ispezionare
campi, costruttori e metodi
Come ultimo esempio, un complete ispezionatore di classi
in grado di fornire informazioni su tutti gli elementi
della classe, compresi campi, metodi e costruttori.
Tutto inizia con un metodo main() che richiede il nome
della classe da visitare:
import
java.io.*;
import java.lang.reflect.*;
import java.lang.annotation.*;
public
class ClassVisitor {
public static void main(String argv[]) throws
Exception {
BufferedReader lineReader =
new BufferedReader(new InputStreamReader(System.in));
System.out.println("Inserire
il nome completo di una classe: ");
String className = lineReader.readLine();
Class c = Class.forName(className);
visit(c);
}
}
Il metodo visit(Class c) deve anzitutto verificare se
il presente oggetto Class rappresenta una classe o uninterfaccia:
public
static void visit(Class c) {
if(c.isInterface())
visitInterface(c);
else
visitClass(c);
}
Il
metodo che ispeziona le classi deve anzitutto raccogliere
informazioni sui fodificatori, quindi sulle annotazioni,
con un apposito metodo parseAnnotatedElement(), quindi
aggiunge la stringa class, il nome della
classe e le eventuali dichiarazioni generiche, attraverso
un apposito metodo parseGenericDeclaration(). Segue
la raccolta di informazioni sulla superclasse e sulle
interfacce implementate. La stringa così creata
viene stampata attraverso un apposito metodo print,
che effettua una stampa formattata, e passa ad esplorare
il contenuto della classe attraverso lapposito
metodo visitClassOrInterfaceDeclarations():
public
static void visitClass(Class c) {
String
description = Modifier.toString(c.getModifiers())+"
";
description
= description + parseAnnotatedElement(c);
description
= description + " class " + c.getSimpleName()
+ parseGenericDeclaration(c)+" ";
Class
superclass = c.getSuperclass();
if(superclass
!= null) {
description
= description + "extends " + superclass.getSimpleName();
description
= description + parseGenericDeclaration(superclass)+"
";
}
Class[]
interfaces = c.getInterfaces();
if(interfaces.length!=0)
{
description
= description + "implements ";
for(Class
i : interfaces) {
description
= description + i.getSimpleName();
description
= description + parseGenericDeclaration(i) + ",";
}
description
= description.substring(0,description.length()-1);
}
print(description);
visitClassOrInterfaceDeclarations(c);
}
Il
metodo visitInterface(Class c) assomiglia molto al precedente,
con la differenza che il metodo getInterfaces() restituisce
le superinterfacce, che vanno pertanto precedute dalla
parola chiave extends. Anche in questo caso
la successiva ispezione viene affidata allapposito
metodo visitClassOrInterfaceDeclarations():
public
static void visitInterface(Class c) {
String
description = Modifier.toString(c.getModifiers())+"
";
description
= description + parseAnnotatedElement(c);
description
= description + "interface" + parseGenericDeclaration(c)
+
c.getSimpleName()
+ " ";
Class[]
interfaces = c.getInterfaces();
if(interfaces.length!=0)
{
description
= description + "extends ";
for(Class
i : interfaces) {
description
= description + i.getSimpleName();
description
= description + parseGenericDeclaration(i) + ",";
}
description
= description.substring(0,description.length()-1);
}
print(description);
visitClassOrInterfaceDeclarations(c);
}
Il
metodo visitClassOrInterfaceDeclarations() chiama il
metodo indent(), che opera una indentazione di un paio
di spazi, e quindi, attraverso i metodi getDeclaredFields(),
getDeclaredConstructors(), getDeclaredMethod() e getDeclaredClasses()
passa in ispezione con appositi metodi gli elementi
dichiarati allinterno delloggetto Class:
public
static void visitClassOrInterfaceDeclarations(Class
c) {
indent();
Field[]
fields = c.getDeclaredFields();
for(Field
f : fields)
visit(f);
Constructor[]
constructors = c.getDeclaredConstructors();
for(Constructor
constructor : constructors)
visit(constructor);
Method[]
methods = c.getDeclaredMethods();
for(Method
m : methods)
visit(m);
Class[]
classes = c.getDeclaredClasses();
for(Class
cl : classes)
visit(cl);
deindent();
}
Il
metodo visit(Field f) raccoglie informazioni sui modificatori,
sulle annotazioni, sul tipo (generico o meno) e infine
sul nome. Quindi invia la stringa in stampa:
public
static void visit(Field f) {
String
description = Modifier.toString(f.getModifiers())+"
";
description
= description + parseAnnotatedElement(f);
description
= description +(Object)f.getGenericType();
description
= description +" " +f.getName();
print(description);
}
Il
metodo visitConstructor(Constructor c) è molto
più complesso del precedente, dal momento che
deve anche raccogliere informazioni su eventuali dichiarazioni
generiche e dei parametri, che a loro volta possono
essere generici. Infine non vanno dimenticate le eccezioni,
che vengono raccolte con lapposito metodo getExceptionTypes():
public
static void visit(Constructor c) {
String
description = Modifier.toString(c.getModifiers())+"
";
description
= description + parseAnnotatedElement(c);
description
= description + parseGenericDeclaration(c);
description
= description + c.getName() + "(";
Class[]
parameters = c.getParameterTypes();
if(parameters.length!=0)
{
for(Class
p : parameters)
description
= description + p.getSimpleName()+parseGenericDeclaration(p)+",";
description
= description.substring(0,description.length()-1);
}
description
= description + ")";
Class[]
exceptions = c.getExceptionTypes();
if(exceptions.length
!= 0) {
description
= description + " throws ";
for(Class
e : exceptions)
description
= description + e.getSimpleName() +parseGenericDeclaration(e)+",";
description
= description.substring(0,description.length()-1);
}
print(description);
}
Il
metodo visit(Method m) è simile al precedente,
con la differenza che un metodo deve avere un tipo di
ritorno che viene cercato col metodo m.getReturnType().getSimpleName(),
seguito da parseGenericDeclaration(m) che permette di
catturare eventuali dichiarazioni generiche nel tipo
di ritorno:
public
static void visit(Method m) {
String
description = Modifier.toString(m.getModifiers())+"
";
description
= description + parseAnnotatedElement(m);
description
= description + m.getReturnType().getSimpleName() +
parseGenericDeclaration(m)
+ " ";
TypeVariable[]
typeVariables = m.getTypeParameters();
for(TypeVariable
typeVariable : typeVariables)
description
= description + typeVariable.getName() +" ";
description
= description + m.getName() +"(";
Class[]
parameters = m.getParameterTypes();
if(parameters.length!=0)
{
for(Class
p : parameters)
description
= description + p.getSimpleName()+parseGenericDeclaration(p)+",";
description
= description.substring(0,description.length()-1);
}
description
= description + ") ";
Class[]
exceptions = m.getExceptionTypes();
if(exceptions.length
!= 0) {
description
= description + "throws ";
for(Class
e : exceptions)
description
= description + e.getSimpleName() +parseGenericDeclaration(e)
+",";
description
= description.substring(0,description.length()-1);
}
print(description);
}
Il
metodo parseAnnotatedElement() restituisce una stringa
contenente le eventuali annotazioni presenti nella classe,
nel campo, nel costruttore o nel metodo specificato:
protected
static String parseAnnotatedElement(AnnotatedElement
annotatedElement) {
Annotation[]
annotations = annotatedElement.getAnnotations();
String
description = "";
for(Annotation
a : annotations)
description
= description + a + " ";
return
description;
}
Infine
il metodo parseGenericDeclaration() ricostruisce le
stringhe che denotano tipi parametrici, tipo <T>
o <K,V>:
protected
static String parseGenericDeclaration(GenericDeclaration
genericDeclaration) {
TypeVariable[]
typeVariables = genericDeclaration.getTypeParameters();
String
description = "";
if(typeVariables.length!=0)
{
description
= "<";
for(TypeVariable
t : typeVariables) {
description
= description + t.getName();
description
= description + ",";
}
description
= description.substring(0,description.length()-1);
description
= description + ">";
}
return
description;
}
Data
la complessità dellesempio, è meglio
presentarne il sorgente completo:
import
java.io.*;
import java.lang.reflect.*;
import java.lang.annotation.*;
public
class ClassVisitor {
private static int spaces = 0;
protected static void indent() {
spaces++;
}
protected static void deindent() {
spaces --;
}
protected static void print(String s) {
for(int i=0 ; i<spaces ;
i++)
System.out.print("
");
System.out.println(s);
}
public static void visit(Class c) {
if(c.isInterface())
visitInterface(c);
else
visitClass(c);
}
public
static void visitClass(Class c) {
String description = Modifier.toString(c.getModifiers())+"
";
description = description +
parseAnnotatedElement(c);
description = description +
" class " + c.getSimpleName() + parseGenericDeclaration(c)+"
";
Class superclass = c.getSuperclass();
if(superclass != null) {
description = description
+ "extends " + superclass.getSimpleName();
description = description
+ parseGenericDeclaration(superclass)+" ";
}
Class[]
interfaces = c.getInterfaces();
if(interfaces.length!=0) {
description = description
+ "implements ";
for(Class i : interfaces)
{
description
= description + i.getSimpleName();
description
= description + parseGenericDeclaration(i) + ",";
}
description = description.substring(0,description.length()-1);
}
print(description);
visitClassOrInterfaceDeclarations(c);
}
public static void visitInterface(Class
c) {
String description = Modifier.toString(c.getModifiers())+"
";
description = description +
parseAnnotatedElement(c);
description = description +
"interface" +
parseGenericDeclaration(c)
+ c.getSimpleName() + " ";
Class[] interfaces = c.getInterfaces();
if(interfaces.length!=0) {
description = description
+ "extends ";
for(Class i : interfaces)
{
description
= description + i.getSimpleName();
description
= description + parseGenericDeclaration(i) + ",";
}
description = description.substring(0,description.length()-1);
}
print(description);
visitClassOrInterfaceDeclarations(c);
}
public static void visitClassOrInterfaceDeclarations(Class
c) {
indent();
Field[] fields = c.getDeclaredFields();
for(Field f : fields)
visit(f);
Constructor[] constructors =
c.getDeclaredConstructors();
for(Constructor constructor
: constructors)
visit(constructor);
Method[] methods = c.getDeclaredMethods();
for(Method m : methods)
visit(m);
Class[] classes = c.getDeclaredClasses();
for(Class cl : classes)
visit(cl);
deindent();
}
public
static void visit(Constructor c) {
String description = Modifier.toString(c.getModifiers())+"
";
description = description +
parseAnnotatedElement(c);
description = description +
parseGenericDeclaration(c);
description = description +
c.getName() + "(";
Class[] parameters = c.getParameterTypes();
if(parameters.length!=0) {
for(Class p : parameters)
description
= description + p.getSimpleName()+parseGenericDeclaration(p)+",";
description
= description.substring(0,description.length()-1);
}
description = description +
")";
Class[] exceptions = c.getExceptionTypes();
if(exceptions.length != 0) {
description = description
+ " throws ";
for(Class e : exceptions)
description
= description + e.getSimpleName() +parseGenericDeclaration(e)+",";
description
= description.substring(0,description.length()-1);
}
print(description);
}
public
static void visit(Field f) {
String description = Modifier.toString(f.getModifiers())+"
";
description = description +
parseAnnotatedElement(f);
description = description +(Object)f.getGenericType();
description = description +"
" +f.getName();
print(description);
}
public
static void visit(Method m) {
String description = Modifier.toString(m.getModifiers())+"
";
description = description +
parseAnnotatedElement(m);
description = description +
m.getReturnType().getSimpleName() +
parseGenericDeclaration(m)
+ " ";
TypeVariable[] typeVariables
= m.getTypeParameters();
for(TypeVariable typeVariable
: typeVariables)
description = description
+ typeVariable.getName() +" ";
description = description +
m.getName() +"(";
Class[] parameters = m.getParameterTypes();
if(parameters.length!=0) {
for(Class p : parameters)
description
= description + p.getSimpleName()+parseGenericDeclaration(p)+",";
description = description.substring(0,description.length()-1);
}
description = description + ") ";
Class[] exceptions = m.getExceptionTypes();
if(exceptions.length != 0) {
description = description +
"throws ";
for(Class e : exceptions)
description = description +
e.getSimpleName() +parseGenericDeclaration(e) +",";
description = description.substring(0,description.length()-1);
}
print(description);
}
protected
static String parseAnnotatedElement(AnnotatedElement
annotatedElement) {
Annotation[] annotations = annotatedElement.getAnnotations();
String description = "";
for(Annotation a : annotations)
description = description
+ a + " ";
return description;
}
protected
static String parseGenericDeclaration(GenericDeclaration
genericDeclaration) {
TypeVariable[] typeVariables
= genericDeclaration.getTypeParameters();
String description = "";
if(typeVariables.length!=0)
{
description = "<";
for(TypeVariable
t : typeVariables) {
description
= description + t.getName();
description
= description + ",";
}
description = description.substring(0,description.length()-1);
description = description
+ ">";
}
return description;
}
public
static void main(String argv[]) throws Exception {
BufferedReader lineReader =
new BufferedReader(new InputStreamReader(System.in));
System.out.println("Inserire
il nome completo di una classe: ");
String className = lineReader.readLine();
Class c = Class.forName(className);
visit(c);
}
}
Al
momento dellesecuzione viene richiesto di specificare
una classe: si provi con classi contenenti annotazioni
@deprecated, tipo java.lang.String, o classi contenenti
tipi parametrici, come java.util.Vector, java.util.List
o java.util.Map.
Conclusioni
Questo mese è stato analizzato luso pratico
dellAPI reflection, attraverso una serie di esempi
che ha permesso di studiare in modo esaustivo le potenzialità
dellAPI stessa. Il mese prossimo verranno approfondite
ulteriormente le API al fine di comprendere i meccanismi
attraverso i quali la reflection permette di manipolare
gli oggetti.
Allegati
Scarica qui i sorgenti
presentati nell'articolo
|