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

  • get\<elemento\> . Significa che lanciando questo tipo si otterranno gli elementi dichiarati public.
  • getDeclared\<elemento\>. Significa che lanciando questo tipo si otterranno tutti gli elementi.

Il package java.lang.reflect.

Le classi del package java.lang.reflect sono :

  • java.lang.reflect.Method
  • java.lang.reflect.Field
  • java.lang.reflect.Constructor
  • java.lang.reflect.Array
  • java.lang.reflect.Modifier

Le prime tre classi implementano l'interfaccia java.lang.reflect.Member che è composta dei seguenti metodi :

  • public Class getDeclaringClass() che permette di ottenere informazioni sia sulle classi che sulle interfacce
  • public Class getName() che permette di ottenere informazioni sui nomi degli elementi della classe
  • public Class getModifier() che permette di ottenere informazioni sui modificatori.

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:

  • Leggere tutti i metodi.
  • Cercare i metodi che iniziano per get. Allora la parte rimanente è la proprietà che è possibile leggere.
  • Cercare i metodi che iniziano per set. Allora la parte rimanente è la proprietà che è possibile scrivere.

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

  • public static BeanInfo getBeanInfo(Class beanClass)
  • public static BeanInfo getBeanInfo(Class beanClass, Class stopClass)

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

  • Gerarchia (metodo costruisciGerarchia() ). Mediante questa funzione è possibile stabilire la gerarchia degli oggetti e le interfacce associate alla classe in oggetto. Per questo si sono utilizzate le seguenti istruzioni
while ( istanzaClasse.getSuperclass() != null ) {
    istanzaClasse = istanzaClasse.getSuperclass();
    taRiflessione.setText(taRiflessione.getText()+tabs + istanzaClasse.getName()+"\n");
    tabs = "  "+ tabs;
}

Tramite il metodo getSuperclass() è possibile risalire al padre della classe e così via fino alla classe oggetto

Class interfacce[] = istanzaClasse.getInterfaces();

Tramite il metodo getInterfaces()è possibile ottenere le interfacce, e quindi tramite il metodo getName() siamo in grado di avere il nome dell'interfaccia.

  • Metodi (metodo ottieniMetodi() ). Mediante questa funzionalità è possibile avere tutti i metodi pubblici della classe con relativi costruttori.

Per realizzare si sono utilizzati i metodi getConstructors() e getMethods() della classe Class.

  • Variabili (metodo ottieniVariabili() ). Mediante questa funzionalità è possibile avere tutte le variabili pubbliche della classe.

Per realizzare ciò sono state usare le seguenti istruzioni

Field[] campi = istanzaClasse.getFields();
  • Proprietà (metodo ottieniProprieta() ). E' risaputo, secondo lo standard seguito dai JavaBeans,.che le proprietà di un oggetto sono riconosciute dai vari ambienti dove si trova la classe se seguono le seguenti regole

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