MokaByte Numero 17 - Marzo 1998

 
Riflessione 
di
Daniela Ruggeri
Vediamo come si utilizza la riflessione e l'introspezione in Java


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.



 Introduzione
 La classe java.lang.Class
 Il package java.lang.reflect
 La classe java.beans.Introspector
 Esempio
 Conclusione


Introduzione

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 classe java.lang.Class.

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

Il package java.lang.reflect.

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:

 La classe Introspector invece conosce già gli standards con i quali è costruito un JavaBean, ed è per questo che conviene usarla evitando in tal modo errori di logica o dovuti a cattive interpretazioni degli standards. I metodi sono tutti statici in modo che non sia necessario istanziare un oggetto Introspector.
Tra i metodi notiamo
 

Esempio.

 
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:

 
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);

        }



}
 
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.

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
 

Per realizzare si sono utilizzati i metodi getConstructors() e getMethods() della classe Class. Per realizzare ciò sono state usare le seguenti istruzioni
Field[] campi = istanzaClasse.getFields();
  1. Le proprietà di lettura iniziano hanno metodi di gestione con nomi del tipo get<nomeproprietà> o del tipo is<nomeproprietà> nel caso di variabili di tipo booleano.
  2. Le proprietà di scrittura iniziano hanno metodi di gestione con nomi del tipo set<nomeproprietà> .
In realtà è anche possibile cambiare queste regole creando una classe che estenda la classe java.bean.BeanInfo (ciò che prende il nome di personalizzazione di un bean), che abbia un nome <nomebean>BeanInfo e utilizzando metodi appositi che servano a fornire informazioni sulle nuove regole; non è trattazione di questo articolo spiegare questa classe, ma è bene che il lettore la tenga presente quando affronterà il discorso dell'introspezione di un bean.

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.
 
 

Vediamo ora relativamente alle proprietà quale sarebbe stato il codice del metodo ottieniProprieta() se invece di utilizzare la riflessione avessimo usato l'introspezione, cioè se invece di utilizzare le classi del package java.lang.reflect e la classe java.lang.Class.

Il codice sarebbe stato il seguente:

    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) {}



    }
 
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.
Analizzando il codice notiamo che per poter ottenere la classe java.beans.BeanInfo è necessario usare la classe java.beans.Introspector in questo modo:
bi = Introspector.getBeanInfo(istanzaClasse)
dove bi è un'istanza della classe BeanInfo

Dopo di che per ottenere le proprietà della classe basta utilizzare

proprieta = bi.getPropertyDescriptors()
dove proprieta è un array di PropertyDescriptor che contiene tutte le informazioni sulle prorpietà.

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().

 

Conclusione.

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.

Daniela Ruggeri

 
 
 
 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it