Java permette di definire una classe (o interfaccia) all’interno di un’altra.
Queste classi vengono chiamate “interne” o “annidate”.
Tale meccanismo arricchisce le possibilità di relazioni tra classi, introducendo in particolare nuove regole di visibilità.
Se la definizione di una classe si trova all’interno di un metodo, tale classe viene chiamata “locale”.
Infine, una classe locale può anche essere “anonima”, quando il suo nome non sarebbe rilevante e/o utile.
Oltre a campi e metodi, una classe può contenere altre classi o interfacce, dette “interne”.
Una classe che non sia interna viene chiamata “top-level”.
A differenza delle classi top-level, le classi interne possono avere tutte le quattro visibilità ammesse dal linguaggio.
La visibilità di una classe interna X stabilisce quali classi possono utilizzarla (cioè, istanziarla, estenderla, dichiarare riferimenti o parametri di tipo X, etc.).
Consideriamo il seguente esempio:
public class A {
private class B {
...
}
class C {
...
}
}
In quest’esempio, la classe B non è visibile al di fuori di A, mentre la classe C è visibile a tutte le classi che si trovano nello stesso pacchetto di A.
Dall’esterno di A, i nomi completi delle classi B e C sono A.B e A.C, rispettivamente.
La visibilità di una classe interna non ha alcun effetto sul codice che si trova all’interno della classe che la contiene.
Ad esempio, la classe B dell’esempio precedente è visibile a tutto il codice contenuto in A, compreso il codice contenuto in altre classi interne ad A, come ad esempio C.
Lo stesso discorso si applica per i campi e i metodi di una classe interna,i loro attributi di visibilità hanno effetto solo sul codice esterno alla classe contenitrice.
In altre parole, tra classi contenute nella stessa classe non vige alcuna restrizione di visibilità.
Ciascun oggetto di una classe interna (non statica, come spiegato dopo) possiede un riferimento implicito ad un oggetto della classe contenitrice.
Tale riferimento viene inizializzato automaticamente al momento della creazione dell’oggetto.
Tale riferimento non può essere modificato.
Supponiamo che B sia una classe interna di A.
Se viene creato un oggetto di tipo B in un contesto non statico della classe A, il riferimento implicito verrà inizializzato con il valore corrente di this.
In tutti gli altri casi, è necessario utilizzare una nuova forma dell’operatore “new”, ovvero:
<riferimento ad oggetto di tipo A>.new B(…).
All’interno della classe B, la sintassi per denotare questo riferimento implicito è A.this.
Nei contesti statici di B, siccome non è disponibile this, non è disponibile neanche A.this.
L’uso di A.this è facoltativo, come quello di this.
Cioè, si può accedere ai campi e ai metodi della classe A anche direttamente.
Le classi interne possono essere statiche o meno.
Una classe interna dichiarata nello scope di classe (cioè al di fuori di metodi e inizializzatori) è statica se preceduta dal modificatore “static”.
Una classe interna dichiarata all’interno di un metodo o di un inizializzatore eredita il proprio essere statica o meno dal metodo in cui è contenuta o dal campo che si sta inizializzando.
Le classi interne statiche non possiedono il riferimento implicito alla classe contenitrice.
Riassumendo, le classi interne non statiche godono delle seguenti proprietà distintive:
Le classi interne statiche godono solo delle prime due proprietà.
Alcuni testi si riferiscono a tutte le classi interne come “annidate” e riservano il termine “interne” solo alle classi interne non statiche.
Una classe interna dichiarata all’interno di un metodo viene chiamata classe locale.
Una classe locale non ha specificatore di visibilità, in quanto è visibile solo all’interno del metodo in cui è dichiarata.
Una classe locale non può avere il modificatore static, in quanto eredita il suo essere statica o meno dal metodo in cui è dichiarata.
Oltre a godere delle proprietà comuni alle classi interne, le classi locali “vedono” le variabili locali e i parametri formali del metodo in cui sono contenute, a patto che essi siano final, cioè costanti.
La slide successiva illustra questa, apparentemente arbitraria, restrizione.
Gli esemplari di una classe locale possono vivere più a lungo del metodo in cui la loro classe è visibile.
Tipicamente, questo succede quando un oggetto di una classe locale viene restituito dal metodo, mascherato da una superclasse o super-interfaccia nota all’esterno.
In questi casi, l’accesso alle variabili locali (compresi i parametri formali) di quel metodo può avvenire quando quel metodo è ormai terminato, cancellando di fatto quelle variabili.
Si ricordi che le variabili locali e i parametri formali sono allocati sullo stack e quindi vengono cancellati al termine di ciascuna invocazione del metodo.
Pertanto, per dare l’illusione al programmatore di accedere a quelle variabili, il compilatore in realtà inserisce negli oggetti delle classi locali delle copie di quelle variabili.
Se il meccanismo funzionasse anche per variabili non final, il “trucco” delle copie delle variabili locali diventerebbe visibile al programmatore, invece che nascosto.
Infatti, potrebbe accadere che una variabile locale viene modificata dopo la creazione di un oggetto della classe interna.
L’oggetto della classe interna, interrogato successivamente, ricorderebbe il vecchio valore di quella variabile, invece del valore modificato.
Talvolta una classe interna viene utilizzata soltanto una volta, tipicamente per istanziare un oggetto che poi viene mascherato con una classe o interfaccia nota.
In questi casi, si può utilizzare una classe anonima.
Una classe anonima si definisce a partire da una classe o una interfaccia esistente.
Nella tabella in figura, a sinistra riportiamo la sintassi per la creazione di classi anonime a partire da una classe o interfaccia esistente.
A destra riportiamo il costrutto equivalente utilizzando normali classi con nome.
Ad esempio, nella libreria AWT (Abstract Windowing Toolkit) per le interfacce grafiche, l’interfaccia ActionListener rappresenta un oggetto in grado di rispondere ad un evento
interface ActionListener {
void actionPerformed(ActionEvent e);
}
La seguente classe utilizza una classe anonima per restituire un oggetto di tipo ActionListener
Mostra codiceLa figura rappresenta il memory layout delle classi definite di seguito.
Notate in particolare la differenza tra classe interna e sottoclasse.
Mostra codiceNell’ambito di un programma di geometria, si implementi la classe Triangolo, il cui costruttore accetta le misure dei tre lati. Se tali misure non danno luogo ad un triangolo, il costruttore deve lanciare un’eccezione. Il metodo getArea restituisce l’area di questo triangolo.
Si implementino anche la classe Triangolo.Rettangolo, il cui costruttore accetta le misure dei due cateti, e la classe Triangolo.Isoscele, il cui costruttore accetta le misure della base e di uno degli altri lati.
Si ricordi che:
4. Risoluzione dell'overloading e dell'overriding
5. Controllo di uguaglianza tra oggetti
6. Classi interne, locali ed anonime
7. Iteratori, teoria e pratica
8. Clonazione di oggetti. Confronto tra oggetti.
9. Elementi di programmazione di interfacce grafiche
10. Il paradigma Model-View-Controller. Il pattern Strategy
11. I pattern Composite e Decorator
12. I pattern Template Method e Factory Method
13. Classi e metodi parametrici
14. La libreria Java Collection Framework: le interfacce Iterable, ...
15. La libreria Java Collection Framework: la classe HashSet e le l...
16. Parametri di tipo con limiti
17. L'implementazione della programmazione generica: la cancellazio...
18. La riflessione
19. Introduzione al multi-threading
22. Classi enumerate