MokaByte
Numero 17 - Marzo 1998
|
|||
|
|
||
Daniela Ruggeri |
|
||
In questo capitolo affrontiamo il concetto di riflessione e introspezione. Le API che gestiscono questi due aspetti sono estremamente semplici e quindi l'articolo può essere letto anche da chi é alle prime esperienze Java purché conosca i componenti fondamentali di una classe, previsti del resto nella programmazione OO.
Il concetto di
riflessione è molto semplice in quanto comporta solo l'utilizzo
dei metodi delle API java.lang.reflect e della classe java.lang.Class
avendo solo la sola conoscenza dei componenti di una classe java, cioè
metodi, variabili ecc.
Il termine riflessione
deriva dal concetto di core reflection chiamato così
perché riflette la struttura dati del nucleo della Java Virtual
Machine. Questo concetto viene anche denominato core reflection a basso
livello.
La specializzazione
della riflessione nei JavaBeans prende il nome di introspezione
e viene gestita dalla classe java.beans.Introspector. Questo
concetto viene anche denominato core reflection ad alto livello
In figura 1.
possiamo vedere le classi componenti la riflessione.
Figura
1.
La prima cosa da notare è che Class implementa l'interfaccia Serializable, che significa che questo oggetto può essere serializzato.
Tra i metodi notiamo:
public Field[] getFields() throws SecurityException public Method[] getMethods() throws SecurityException public Field getField(String name) throws NoSuchFieldException, SecurityException public Method getMethod(String name, Class[] parameterTypes) throws NoSuchMethodException, SecurityException public Class[] getDeclaredClasses() throws SecurityException public Field[] getDeclaredFields() throws SecurityException public Method[] getDeclaredMethods() throws SecurityException public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException public Method getDeclaredMethod(String name, Class[] parameterTypes) throws NoSuchMethodException, SecurityException
Come possiamo vedere esistono tre gruppi di metodi ognuno dei quali serve ad ottenere rispettivamente i nomi dei campi (Field), dei metodi (Method) e dei costruttori (Constructor). In ogni gruppo sono presenti due tipi di metodi
Le classi del
package java.lang.reflect sono :
Le prime
tre classi implementano l'interfaccia java.lang.reflect.Member che
è composta dei seguenti metodi :
Le tre classi
non fanno altro che fornire informazioni più dettagliate su una
delle categorie Member.
La classe Array
permette di costruire molto facilmente matrici multidimensionali di oggetti.
Con l'uso di questa classe invece del tipo primitivo equivalente (array
di oggetti) si può decidere di definire la dimensione in un secondo
momento invece che al momento della definizione.
La classe Modifier
permette di conoscere informazioni dettagliate sui modificatori.
La
classe java.beans.Introspector.
In teoria potremmo usare le classi del package java.lang.reflect per ottenere tutte le informazioni relative al JavaBeans. Tuttavia per poterlo fare noi dovremmo scrivere un programma ad hoc per ottenere queste informazioni sulla base degli standard definiti per i beans. Per esempio per conoscere le proprietà dovremmo:
Passiamo ora
a considerare un semplice esempio di riflessione.
Per questo esempio abbiamo usato le API riflessive che leggono solo gli elementi di tipo pubblico in quanto i maggiori browers che vengono comunemente utilizzati sollevano eccezioni di sicurezza quando si tenta di leggere elementi di altro tipo. Nulla vieta però allo sviluppatore di cambiare il codice in modo che possa leggere gli elementi con ogni tipo di modificatore.
Per provare l'esempio è stato utilizzato il browser Communicator 4.03. La gestione degli eventi è quella vecchia visto che probabilmente non tutti i programmatori installano la patch aggiuntiva per questo browser contenente le classi che gestiscono la nuova gestione eventi.
La sua realizzazione è mostrata di seguito:
Il codice è il seguente:
Come potete vedere nell'applet è presente sia una lista con delle classi proposte da utilizzare, sia un campo di tipo TextField dove poter digitare una classe a piacimento presente nelle classi associate al browser, se si utilizza il browser, o presenti nella variabile di ambiente CLASSPATH, se si utilizza AppletViewer in ambiente DOS.import java.awt.*; import java.applet.*; import java.beans.*; import java.lang.reflect.*; import java.util.Vector; import java.util.Hashtable; import java.beans.*; public class Rifletti extends Applet { java.awt.Checkbox rScelta1; CheckboxGroup Group1; java.awt.Checkbox rScelta2; java.awt.Button bGerarchia; java.awt.TextArea taRiflessione; java.awt.Button bMetodi; java.awt.List lClasse; java.awt.Button bVariabili; java.awt.TextField tClasse; java.awt.Button bProprieta; void rScelta1_Action(Event event) { // Abilita scelta Classe da List lClasse.enable(); tClasse.disable(); } void rScelta2_Action(Event event) { // Abilita digitazione manuale Classe da TextField tClasse.enable(); lClasse.disable(); tClasse.requestFocus(); } public void init() { super.init(); setLayout(null); addNotify(); resize(781,495); setBackground(new Color(255)); Group1 = new CheckboxGroup(); rScelta1 = new java.awt.Checkbox("Selezione da lista", Group1, true); rScelta1.reshape(24,240,205,18); rScelta1.setFont(new Font("TimesRoman", Font.BOLD, 14)); rScelta1.setForeground(new Color(16776960)); add(rScelta1); rScelta2 = new java.awt.Checkbox("Digitazione di una classe", Group1, false); rScelta2.reshape(24,288,194,19); rScelta2.setFont(new Font("TimesRoman", Font.BOLD, 14)); rScelta2.setForeground(new Color(16776960)); add(rScelta2); bGerarchia = new java.awt.Button("Gerarchia"); bGerarchia.reshape(24,420,151,24); add(bGerarchia); taRiflessione = new java.awt.TextArea(); taRiflessione.reshape(228,12,520,385); taRiflessione.setBackground(new Color(16777215)); add(taRiflessione); bMetodi = new java.awt.Button("Metodi"); bMetodi.reshape(204,420,151,24); add(bMetodi); lClasse = new java.awt.List(0,false); lClasse.addItem("Rifletti"); lClasse.addItem("java.util.Vector"); lClasse.addItem("java.awt.Button"); lClasse.addItem("java.net.HttpURLConnection"); lClasse.addItem("java.io.PrintWriter"); lClasse.addItem("java.io.PushbackInputStream"); lClasse.addItem("java.beans.PropertyChangeSupport"); lClasse.addItem("java.beans.BeanInfo"); lClasse.addItem("java.lang.Thread"); lClasse.addItem(""); add(lClasse); lClasse.reshape(12,12,218,134); lClasse.setBackground(new Color(16777215)); bVariabili = new java.awt.Button("Variabili"); bVariabili.reshape(384,420,151,24); add(bVariabili); tClasse = new java.awt.TextField(); tClasse.disable(); tClasse.reshape(12,360,204,32); tClasse.setBackground(new Color(16777215)); add(tClasse); bProprieta = new java.awt.Button("Proprietà"); bProprieta.reshape(564,420,151,24); add(bProprieta); lClasse.select(0); } public boolean handleEvent(Event event) { String nomeClasse = lClasse.getSelectedItem(); Class istanzaClasse= this.getClass(); if (event.target instanceof Button && event.id == Event.ACTION_EVENT) { if (rScelta2.getState()==true) nomeClasse = tClasse.getText(); try { // Ottiene la classe istanzaClasse = Class.forName(nomeClasse); } catch (ClassNotFoundException e) { (new fErrore("Finestra Messaggi","Classe non trovata: "+ nomeClasse)).show(); return false; } catch (IllegalArgumentException e) { (new fErrore("Finestra Messaggi","Digitare Classe")).show(); return false; } taRiflessione.setText("Oggetto di Tipo '"+ nomeClasse+"'\n\n"); } if (event.target == rScelta2 && event.id == Event.ACTION_EVENT) { rScelta2_Action(event); return true; } if (event.target == rScelta1 && event.id == Event.ACTION_EVENT) { rScelta1_Action(event); return true; } if (event.target == bGerarchia && event.id == Event.ACTION_EVENT) { costruisciGerarchia(istanzaClasse); return true; } if (event.target == bMetodi && event.id == Event.ACTION_EVENT) { ottieniMetodi(istanzaClasse); return true; } if (event.target == bVariabili && event.id == Event.ACTION_EVENT) { ottieniVariabili(istanzaClasse); return true; } if (event.target == bProprieta && event.id == Event.ACTION_EVENT) { ottieniProprieta1(istanzaClasse); return true; } return super.handleEvent(event); } public void costruisciGerarchia(Class istanzaClasse) { Object oggetto=this; taRiflessione.setText(taRiflessione.getText()+"L'oggetto eredita da:\n"); String tabs = " ->"; // determina le interfacce Class interfacce[] = istanzaClasse.getInterfaces(); // determina l'albero genealogico while ( istanzaClasse.getSuperclass() != null ) { istanzaClasse = istanzaClasse.getSuperclass(); taRiflessione.setText(taRiflessione.getText()+tabs + istanzaClasse.getName()+"\n"); tabs = " "+ tabs; } taRiflessione.setText(taRiflessione.getText()+"\ne implementa le seguenti interfacce:\n"); if ( interfacce.length > 0 ) { for (int i=0; i < interfacce.length; i++) { taRiflessione.setText(taRiflessione.getText()+"-"+ interfacce[i].getName()+"\n"); } } else { taRiflessione.setText(taRiflessione.getText()+"Nessuna\n"); } } public void ottieniMetodi(Class istanzaClasse) { taRiflessione.setText(taRiflessione.getText()+"Elenco dei Costruttori:\n"); Constructor[] costruttori = {}; // Ottiene i costruttori try { costruttori = istanzaClasse.getConstructors(); } catch (SecurityException e) { System.out.println("errore!!"); } if (costruttori.length > 0) { for (int i = 0; i < costruttori.length; i++) taRiflessione.setText(taRiflessione.getText()+costruttori[i].toString()+"\n"); } else { taRiflessione.setText(taRiflessione.getText()+"Nessuno\n"); } taRiflessione.setText(taRiflessione.getText()+"\nElenco dei Metodi:\n"); // Ottiene metodi pubblici Method[] metodi = {}; try { metodi = istanzaClasse.getMethods(); } catch (SecurityException e) { System.out.println("errore!!"); } for (int i = 0; i < metodi.length; i++) taRiflessione.setText(taRiflessione.getText()+metodi[i].toString()+"\n"); } public void ottieniVariabili(Class istanzaClasse) { taRiflessione.setText(taRiflessione.getText()+"Elenco Variabili:"); // Ottiene variabili pubbliche Field[] campi = istanzaClasse.getFields(); for (int i=0; i < campi.length; i++) { taRiflessione.setText(taRiflessione.getText()+"\n"+campi[i].toString()); } } public void ottieniProprieta(Class istanzaClasse) { Hashtable proprietaLettura = new Hashtable(); Hashtable proprietaScrittura = new Hashtable(); Vector proprieta = new Vector(); Method[] metodiPubblici; String nomeMetodo; String nomeProprieta = null; metodiPubblici = istanzaClasse.getMethods(); taRiflessione.setText(taRiflessione.getText()+"Elenco proprietà di lettura:\n"); for (int i=0; i < metodiPubblici.length; i++) { nomeMetodo = metodiPubblici[i].getName(); if ( nomeMetodo.startsWith("set")) { nomeProprieta = Character.toLowerCase(nomeMetodo.charAt(3)) + nomeMetodo.substring(4); if ( proprietaScrittura.get(nomeProprieta) == null ) { proprieta.addElement(nomeProprieta); proprietaScrittura.put(nomeProprieta, nomeMetodo); } } else if ( nomeMetodo.startsWith("get") || nomeMetodo.startsWith("is") ) { if ( nomeMetodo.startsWith("get")) nomeProprieta = Character.toLowerCase(nomeMetodo.charAt(3)) + nomeMetodo.substring(4); else nomeProprieta = Character.toLowerCase(nomeMetodo.charAt(2)) + nomeMetodo.substring(3); if ( proprietaLettura.get(nomeProprieta) == null ) { taRiflessione.setText(taRiflessione.getText()+nomeProprieta+"\n"); proprietaLettura.put(nomeProprieta, nomeMetodo); } } else { continue; } } taRiflessione.setText(taRiflessione.getText()+"\nElenco proprietà di scrittura:\n"); for (int i=0; i < proprieta.size(); i++) { nomeProprieta = (String)proprieta.elementAt(i); taRiflessione.setText(taRiflessione.getText()+nomeProprieta+"\n"); } } } // Frame per la gestione degli errori import java.awt.*; public class fErrore extends Frame { java.awt.Button bOK; java.awt.Label lMessaggio; void bOK_Clicked(Event event) { hide(); } public fErrore() { setLayout(null); addNotify(); resize(insets().left + insets().right + 324,insets().top + insets().bottom + 186); setFont(new Font("Dialog", Font.BOLD, 14)); setBackground(new Color(12632256)); bOK = new java.awt.Button("OK"); bOK.reshape(insets().left + 107,insets().top + 120,110,38); add(bOK); lMessaggio = new java.awt.Label("text"); lMessaggio.reshape(insets().left + 24,insets().top + 24,275,36); lMessaggio.setForeground(new Color(16711680)); lMessaggio.setBackground(new Color(12632256)); add(lMessaggio); setTitle("Untitled"); } public fErrore(String title) { this(); setTitle(title); } public fErrore(String title, String messaggio) { this(title); lMessaggio.setText(messaggio); } public synchronized void show() { move(50, 50); super.show(); } public boolean handleEvent(Event event) { if (event.id == Event.WINDOW_DESTROY) { hide(); // hide the Frame return true; } if (event.target == bOK && event.id == Event.ACTION_EVENT) { bOK_Clicked(event); return true; } return super.handleEvent(event); } }
Per scegliere dalla lista basta cliccare su "Selezione da lista", e per una scelta manuale nel TextField basta cliccare su "Digitazione di una classe".
Come potete vedere il programma presenta 4 bottoni cliccando sui quali viene determinata la classe mediante l'istruzione:
istanzaClasse = Class.forName(nomeClasse);dove istanzaClasse è un oggetto di tipo Class
I bottoni che
realizzano le funzionalità atte a guardare all'interno del codice
sono
Tramite il metodo getSuperclass() è possibile risalire al padre della classe e così via fino alla classe oggettowhile ( istanzaClasse.getSuperclass() != null ) { istanzaClasse = istanzaClasse.getSuperclass(); taRiflessione.setText(taRiflessione.getText()+tabs + istanzaClasse.getName()+"\n"); tabs = " "+ tabs; }
Tramite il metodo getInterfaces()è possibile ottenere le interfacce, e quindi tramite il metodo getName() siamo in grado di avere il nome dell'interfaccia.Class interfacce[] = istanzaClasse.getInterfaces();
Field[] campi = istanzaClasse.getFields();
Il codice per realizzare ciò è molto semplice e consiste dopo aver individuato i metodi pubblici con l'istruzione getMethods(), nel scorrerli tutti alla ricerca di quelli che iniziano per get, set o is.
Da notare l'utilizzo della classe Hashtable (utile per gestire in maniera ottimale una collezione di oggetti).
In questo programma
è stata utilizzata per raccogliere le proprietà di lettura
e di scrittura, e poi vedere se nella ricerca delle proprietà analizzando
i metodi, non si verifichi che una proprietà venga conteggiata più
volte, in quanto associata allo stesso nome di più metodo che differiscono
dal tipo e/o dal numero dei parametri.
Il codice sarebbe stato il seguente:
Praticamente si può dire l'introspezione "conosce" già lo standard con cui vengono costruiti i bean (metodi, eventi, proprietà), perché queste informazioni sono contenute nella classe java.beans.BeanInfo.public void ottieniProprieta(Class istanzaClasse) { BeanInfo bi; PropertyDescriptor[] proprieta; try { bi = Introspector.getBeanInfo(istanzaClasse) ; proprieta = bi.getPropertyDescriptors(); taRiflessione.setText(taRiflessione.getText()+"Elenco proprietà di lettura:\n"); for (int i=0; i < proprieta.length; i++) { if (proprieta[i].getReadMethod() != null) taRiflessione.setText(taRiflessione.getText()+proprieta[i].getName()+"\n"); } taRiflessione.setText(taRiflessione.getText()+"\nElenco proprietà di scrittura:\n"); for (int i=0; i < proprieta.length; i++) { if (proprieta[i].getWriteMethod() != null) taRiflessione.setText(taRiflessione.getText()+proprieta[i].getName()+"\n"); } } catch (java.beans.IntrospectionException e) {} }
dove bi è un'istanza della classe BeanInfobi = Introspector.getBeanInfo(istanzaClasse)
Dopo di che per ottenere le proprietà della classe basta utilizzare
dove proprieta è un array di PropertyDescriptor che contiene tutte le informazioni sulle prorpietà.proprieta = bi.getPropertyDescriptors()
Per sapere se una proprietà è di lettura o di scrittura a questo punto basta vedere se esiste i metodi associati di get o set rispettivamente tramite i metodi getReadMethod() e getWriteMethod().
Concludendo possiamo dire che nel prossimo futuro conviene utilizzare l'introspezione più che la riflessione, primo perché si tratta di una forma più organizzata di programmazione e secondo perché presto la tecnica dei JavaBean verrà sempre più utilizzata.
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|