MokaByte Numero 12 - Ottobre 1997
Foto
Inner Class 
 
 
di
Daniela Ruggeri
 
Una valida alternativa alle Classi Top-Level 

 

 
 



 

Fino alla versione del JDK 1.0.2. Java supportava solo le classi ad alto livello (top-level classes), classi cioè create esplicitamente e che entravano a far parte dei packages. Vediamo ora come funzionano le Inner Class messe a disposizione a partire dal JDK 1.1.


                            Introduzione
                            Come lavorano le Inner Classes?
                            Tipi di Inner Class
                            Perché le Inner Class?
                            Conclusione 


Introduzione

Nella versione 1.1, sono state rese disponibili le cosiddette Inner Class o classi interne definite internamente ad altre classi o metodi di altri classi entro un blocco di istruzioni o anonimamente entro un'espressione. Per contrapposizione le classi top-level vengono chiamate Outer Class.

Queste classi sono invisibili all'esterno e sono utilizzabili solo all'interno della classe che le include.

Le Inner classes nascono dalla combinazione di un blocco strutturato di istruzioni e la programmazione basata sulle classi. La programmazione in questo modo diventa più ricca e flessibile, permettendo allo sviluppatore Java di gestire singoli blocchi di istruzione interni alla classe top-level, che si comportano come veri e propri oggetti istanze di classi.

Tipi di Inner Class

In questo paragrafo presenteremo esempi di vari tipi di Inner Class. Presenteremo dunque le Inner Class interne alle classi top-level, quelle interne a metodi di classi top-level e le classi anonime.

Esempio di Inner Class interna ad una classe:
// Outer Class

public class MiaClasse {
        ..... istruzioni
        MiaInnerClass  classeinterna = new MiaInnerClass();
        ..... istruzioni

// Inner Class

        class MiaInnerClass extends AltraClasse {
                MiaInnerClass () {
                        ....istruzioni
                } // fine costruttore
        } // fine Inner Class
} // Fine Outer Class

Per quanto riguarda le applicazioni, se si fa riferimento alle Inner Class nel metodo main( ), bisogna prima di tutto istanziare l'applicazione e poi riferirsi alle inner class come parte dell'applicazione.

Esempio:

public class MiaApplicazione {

// Inner Class

        class MiaInnerClass extends AltraClasse {
                MiaInnerClass () {
                        ....istruzioni
                } // fine costruttore
        }

        public static void main(String args[]) {
                MiaApplicazione app = new MiaApplicazione;
                MiaApplicazione.MiaInnerClass inner = MiaApplicazione.new MiaInnerClass();
        }
}

Notare l'uso di new associato alla classe outer per istanziare la classe inner.

Esiste anche la possibilità di definire Inner Class statiche, in modo che non sia più necessario istanziare la classe outer per vederle.

Una classe anonima è una notazione abbreviata per creare un semplice oggetto locale in-line" entro ogni espressione, semplicemente inglobando il codice richiesto in una espressione "new".

Esempio di classe anonima:

        UnaClasseAnonima UnMetodo(parametro) {
             return new UnaClasseAnonima() { // inizio specifiche per la classe anonima

                 public boolean UnMetodoDiClasseAnonima() {
                ... istruzioni ...
            }

            ... istruzioni ...

             }; // fine specifiche per la classe anonima. Il carattere ";" è obbligatorio.
         }

Una classe può essere nidificata entro un metodo o entro un qualsiasi altro spazio (per esempio entro una IF).
Vi sono 2 ragioni per il loro uso:
  1. Si vuole implementare un'interfaccia di un qualche tipo e ritornare l'handle della Inner Class così creata.
  2. Si sta risolvendo un complicato problema e si vuole creare una classe che aiuti a risolvere una soluzione ma che nello stesso tempo non sia possibile usare pubblicamente. Si vuole dunque che sia invisibile all'esterno.
 Detto ciò le Inner Class possono essere:

 1. Definite entro un metodo

  // Outer Class
public class MiaClasse {
        ..... istruzioni
        UnInterfacciaEsistente UIE = MioMetodoOuter(); // i metodi definiti in MiaInnerClass si rendono disponibili
        ..... istruzioni

       UnInterfacciaEsistente MioMetodoOuter() {

// Inner Class

             class MiaInnerClass implements UnInterfacciaEsistente {
        ..... istruzioni
             }
             return new MiaInnerClass();
         }

} // Fine Outer Class

2. Definite entro uno spazio di un metodo
 
  • Questo significa che in uno spazio delimitato dalle parentesi {...} all'interno di un metodo abbiamo la possibilità di includere la definizione di una Inner Class.
  •  
    Per esempio:

    if (condizione)
    {
            // Inner Class
            class MiaInnerClass { // inizio definizione inner class
                    .... variabili ...
                    // costruttore
                    MiaInnerClass (tipo parametro) {
                            id = s;
                    }
                    // definizione di un metodo
                    UnTipo UnMetodo() {
                            ... istruzioni ...
                    }
                    ... altri metodi ...
            } // fine definizione Inner Class
            MiaInnerClass mic = new MiaInnerClass(parametro);  // Istanziazione della Inner Class
            UnTipo ut = mic.UnMetodo(); // utilizzo di un metodo della Inner Class
    }
    Interessante è anche la possibilità di "creare un oggetto di una classe anonima che erediti da un'altra Classe".
    Consideriamo l'esempio:

    abstract class ClasseEsterna {
            abstract public int UnMetodo();
        }
    public class ClassePrincipale {
            ... istruzioni ...
            // metodo che restituisce il tipo ClasseEsterna
            public ClasseEsterna cont() {
                    return new ClasseEsterna() { // inizio spazio dedicato alla classe anonima
                            private int i = 11;
                            public int UnMetodo() {
                                    return i;
                            }
                    }; // fine spazio dedicato alla classe anonima. Il carattere ";" è obbligatorio
            }
    }


  • Questa strana sintassi permette alla classe anonima di poter avere dei metodi ridefiniti. Nell'esempio il metodo cont() restituisce un'istanza anonima di una classe che eredita dalla classe ClasseEsterna la quale ha il metodo ridefinito UnMetodo(). Per cui l'istruzione cont().UnMetodo() darà come risultato 11.

    Come lavorano le Inner Classes?
     

    Il codice delle Inner Class è strettamente relativo all'istanza di classe che la include, quindi l'istanza di inner class ha bisogno per lavorare di poter determinare l'istanza della classe che la include.

    Per raggiungere questo scopo, quando il sorgente viene compilato con il compilatore JavaSoft Java 1.1, questo prima di generare il bytecode lo trasforma in codice java compatibile con le specifiche Java 1.0 in cui non erano previste le Inner Class.

    Consideriamo il seguente esempio:

    import java.awt.*;
    import java.awt.event.*;
    public class RoundButton extends Canvas {
        boolean pressed = false;
        int width;
        int height;
        int x;
        int y;

            public RoundButton (){
                    this(10,10,150,150);
            }

            public RoundButton (int x, int y, int width, int height){
                    if (width < 0) {
                            throw new IllegalArgumentException ("Larghezza illegale:" + width);
                    }
                    if (height < 0) {
                            throw new IllegalArgumentException ("Altezza illegale:" + height);
                    }
                    if (x < 0) {
                            throw new IllegalArgumentException ("Coordinata x illegale:" + x);
                    }
                    if (y < 0) {
                            throw new IllegalArgumentException ("Coordinata y illegale:" + y);
                    }
                    this.x = x;
                    this.y = y;
                    this.width = width;
                    this.height = height;
                    addMouseListener (new ClickAdapter());  // Creazione istanza Inner Class
            }

            public void paint (Graphics g) {
                    super.paint(g);
                    if (isEnabled()) {
                       if (pressed)
                            g.setColor (Color.red);
                       else
                            g.setColor (Color.black);
                    } else {
                       g.setColor (Color.gray);
                    }
                    g.fillOval(x,y,width,height);
            }

    // Definizione Inner Class

        private final class ClickAdapter extends MouseAdapter {

            public void mousePressed (MouseEvent e) {
                    if (isEnabled()) {
                        pressed = true;
                        repaint();
                    }
            }

            public void mouseReleased (MouseEvent e) {
                    if (isEnabled()) {
                        pressed = false;
                        repaint();
                    }
            }
        }
    }

    Questo semplice esempio consiste in un cerchio che cambia colore quando ci si clicca sopra.
    La Inner Class è la classe ClickAdapter definita entro la classe RoundButton

    I due stati (nel caso specifico nero e rosso) sono mostrati in figura 1. e 2.


    Figura 1.

    Figura 2.

    Si è scelta una classe Adapter (classe in genere astratta che implementa una EventListener e raccoglie tutti i metodi della stessa; questa classe riceve gli eventi del tipo di listener specificato, e la sua utilità deriva dalla maggiore facilità di creare ascoltatori semplicemente estendendo la EventListener richiesta e ridefininendo i metodi di interesse) come Inner Class, perché risulta piuttosto semplice gestire gli eventi relativi ad un'interfaccia estensione di EventListener mediante la codifica di una classe interna.

    Si acclude anche un esempio di applet che fa uso di questa classe componente:

    public class BottRound extends Applet {
            RoundButton t;
            Button button1;
            public void init() {
                    setLayout(null);
                    addNotify(); resize(426,266);
                    t = new RoundButton (5,5,150,150);
                    t.setBounds(101,79,500,300); add(t);
            }
    }
    Compilando il componente ci accorgiamo che vengono create due classi:
    1. RoundButton.class Outer Class.
    2. RoundButton$ClickAdapter.class Inner Class
    La prima cosa da notare è la presenza del carattere $. Infatti le specifiche del linguaggio Java 1.1. dicono che il nome di un tipo che è una classe membro (inner class), dopo la compilazione e la trasformazione in codice Java 1.0 per la generazione del bytecode JVM , consiste del completo nome qualificato della inner class, eccetto che per il carattere ".", rimpiazzato dal carattere "$".

    Quindi se il nome qualificato della Inner Class è RoundButton.ClickAdapter, il nome della classe è RoundButton$ClickAdapter

    Passiamo ora a considerare il codice trasformato prima della generazione del bytecode.

    La classe RoundButton diventa:

    /* Originally compiled from RoundButton.java */

    import java.awt.*;

    public synchronized class RoundButton extends Canvas
    {
        boolean pressed;
        int width;
        int height;
        int x;
        int y;

        public RoundButton()
        {
            this(10, 10, 150, 150);
        }

        public RoundButton(int i1, int j, int k, int i2)
        {
            pressed = false;
            if (k < 0)
                throw new IllegalArgumentException(new StringBuffer("Larghezza illegale:").append(k).toString());
            if (i2 < 0)
                throw new IllegalArgumentException(new StringBuffer("Altezza illegale:").append(i2).toString());
            if (i1 < 0)
                throw new IllegalArgumentException(new StringBuffer("Coordinata x illegale:").append(i1).toString());
            if (j < 0)
                throw new IllegalArgumentException(new StringBuffer("Coordinata y illegale:").append(j).toString());
            x = i1;
            y = j;
            width = k;
            height = i2;
            addMouseListener(new RoundButton$ClickAdapter(this));
        }

        public void paint(Graphics g)
        {
            super.paint(g);
            if (!isEnabled())
                g.setColor(Color.gray);
            else if (pressed)
                g.setColor(Color.red);
            else
                g.setColor(Color.black);
            g.fillOval(x, y, width, height);
        }
    }

    Qui la prima cosa che notiamo è la trasformazione del parametro entro il metodo addMouseListener in new RoundButton$ClickAdapter(this). In particolare notiamo il passaggio come parametro della classe includente al costruttore di RoundButton$ClickAdapter.
    Notiamo anche il modificatore synchronized. Si sa che l'uso di questo modificatore aumenta l'efficienza di collegamento tra i vari oggetti. Infatti mediante l'uso di synchronized si ottiene il singolo lock associato all'oggetto, con un'attesa per il  lock se l'oggetto non è libero, la successiva esecuzione degli statements che lo riguardano e al termine il successivo rilascio del lock.

    La classe RoundButton$ClickAdapter diventa:

    /* Originally compiled from RoundButton.java */

    import java.awt.Component;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;

    final synchronized class RoundButton$ClickAdapter extends MouseAdapter
    {
        private final RoundButton this$0;

        public void mousePressed(MouseEvent mouseEvent)
        {
            if (this$0.isEnabled())
            {
                this$0.pressed = true;
                this$0.repaint();
            }
        }

        public void mouseReleased(MouseEvent mouseEvent)
        {
            if (this$0.isEnabled())
            {
                this$0.pressed = false;
                this$0.repaint();
            }
        }

        RoundButton$ClickAdapter(RoundButton roundButton)
        {
                    this$0 = roundButton;
        }
    }

     Notiamo anche la dichiarazione final e il modificatore synchronized. Infatti se una variabile locale o parametro in una classe si riferisce ad un'altra classe essa deve essere dichiarata final. A causa dei potenziali problemi di sincronizzazione, non vi è alcun modo per due oggetti di poter condividere i cambiamenti di una variabile locale.
    Note the final declaration. Local final variables such as array are a new feature in 1.1. In fact, if a local variable or
    parameter in one class is referred to by another (inner) class, it must be declared final. Because of potential
    synchronization problems, there is by design no way for two objects to share access to a changeable local variable. The
    state variable count could not be coded as a local variable, unless perhaps it were changed a one-element array:

    Qui invece notiamo:
     

    1. Un nuovo costruttore che prende come parametro la classe RoundButton. In questo modo la Inner Class può ricevere l'handle della classe che la includeva.
    2. La presenza del modificatore synchronized. La sua presenza è stata già spiegata per la classe precedente.
    3. L'aggiunta della variabile privata
    1. private final RoundButton this$0;

    2. e dell'assegnazione nel costruttore

    3.  
    4. this$0 = roundButton;

    5.  Permette alla Inner Class di accedere a tutte le variabili (this$0.pressed) e di utilizzare tutti i metodi (this$0.isEnabled() e this$0.repaint()) della classe che la includere.
      Notiamo anche la dichiarazione final. Infatti se una variabile locale o parametro in una classe si riferisce ad un'altra classe essa deve essere dichiarata final. A causa dei potenziali problemi di sincronizzazione, non vi è alcun modo per due oggetti di poter condividere i cambiamenti di una variabile locale.

    Il formato del nome this$0 è una specifica obbligatoria per i links nella trasformazione della inner class nel linguaggio Java 1.0, in modo che debuggers e tools possano riconoscere tali links facilmente.

    In genere i programmatori sono assolutamenti inconsapevoli dell'avvenuta trasformazione.

     

    Perché le Inner Class?

    Fin dall'inizio della progettazione del linguaggio Java i suoi disegnatori hanno riconosciuto la necessità di avere una sorta di "metodo puntatore" che connettesse pezzi di codice differenti. Questo comporta la gestione di un individuale blocco di codice che può essere usato senza puntamento all'oggetto o classe contenente il codice. In altri linguaggi come C o Lisp le function pointers assolvono questo compito. Per esempio questi puntatori spesso servono a connettere un ritorno o evento di un modulo a un pezzo di codice di un altro modulo.

    Secondo lo stile dei linguaggi orientati agli oggetti, Smalltalk ha dei blocchi che sono grossi pezzi di codice che si comportano come piccoli oggetti.

    Come con le function pointers di C o Lisp, i blocchi Smalltalk possono essere usati per organizzare dei complessi flussi di controllo.

    In Java, gli stessi complessi flussi di controllo inclusi la gestione degli eventi e le iterazioni, sono espressi da classi e interfacce. Java usa le interfacce in un modo simile alle function types di altri linguaggi.

    Il programmatore Java crea le equivalenti di un ritorno o di un blocco Smalltalk racchiudendo il codice previsto in una classe adapter che implementa l'interfaccia richiesta.

    Con le inner classes, l'uso degli adapters è semplice quanto i blocchi Smalltalk, o le Inner functions negli altri linguaggi. Quindi poiché le classi sono più ricche delle funzioni (perché hanno più entry points), gli oggetti adapter Java sono più potenti e più strutturati delle function pointers.

    Così, laddove i programmatori C, Lisp, e Smalltalk usano varianti dei "metodi puntatori"per incapsulare porzioni di codice, i programmatori Java usano gli oggetti.

    Dove altri linguaggi hanno function types specializzate e notations per incapsulare il comportamento come funzioni, Java ha solo tipi di classi ed interfacce.

    In Java, "la classe è il quanto del comportamento". Uno dei benefici di questo approccio è la semplicità e stabilità per la JVM, che non ha bisogno di speciali supporti per le inner classes o funzioni puntatori.

    Senza le inner classes, i programmatori Java possono creare ritorni e iterazioni mediante le classi adapter definite ad alto livello, ma le specifiche possono essere goffe e impraticabili. Mediante le inner classes invece, i programmatori Java possono scrivere concise classi adapter che sono codificate precisamente dove sono necessarie, e operare direttamente sulle variabili interne e i metodi di una classe o blocco.  Da cui, le inner classes fabbricano classi adapter pratiche secondo lo stile della codifica.

     

    Conclusione
    Spero che questo articolo abbia contribuito a chiarire un po' le idee sull'uso di tecniche alternative al fine di rendere più efficienti e manutenibili i propri programmi. Prevediamo che in futuro, le inner classes saranno ancora più efficienti delle loro equivalenti classi adapter ad alto livello, a causa della notevole possibilità nell'aumento nell'ottimizzazione di queste classi inaccessibili dall'esterno. Questo avverrà probabilmente con  l'introduzione della nuova versione del JDK 1.2.

      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