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](#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

private final RoundButton this\$0;

e dell'assegnazione nel costruttore

this\$0 = roundButton;

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