Le Inner Class consentono di definire una
classe dentro un’altra classe. Ciò ha delle particolarità in termini di
visibilità. Di questo aspetto ne
discuteremo in maniera approfondita nel corso dell’articolo. Per ora teniamo
presente che, così come le classi hanno variabili e metodi, una classe può
avere anche una classe come membro.
Per comprendere al meglio facciamo un
piccolo esempio:
Immaginiamo di avere una classe GUI che ha delle
responsabilità per la gestone di azioni per la parte client (legge i messaggi dal
server, invia l’input dell’utente al server e altro). Tutti questi metodi vengono invocati quando
l’utente genere degli eventi, come per esempio il click su un bottone o la pressione
di un tasto sulla tastiera. Quindi, da una parte abbiamo i metodi specifici
della chat e dall’altra abbiamo bisogno di metodi per la gestione degli eventi.
La responsabilità di quest’ultimi è quella di guidare le chiamate ai metodi
specifici del client. Per una buona progettazione OO, la soluzione migliore è
quella di avere i metodi specifici in una classe ChatClient e avere il codice
di gestone degli eventi in una classe sperata.
Le due classi indicate potrebbero essere implementate in
maniera separata. Tuttavia, il codice che invia il messaggio al server deve
leggere i dati da una particolare Text Field. Se un utente clicca il Button A
il programma estrae il testo dalla TextField B di una particolare istanza della
classe ChatClient. In sostanza, il codice per la gestione eventi ha bisogno di
accedere ai membri di ChatClient. Qual è il miglior modo di implementare questa
dipendenza? Potrebbe essere l’ereditarietà ma cosa succede se la classe
ChatClient eredita qualcosa da un’altra classe e il codice di gestione
dell’evento deve estendere qualche altra classe? In Java non c’è ereditarietà
multipla. Senza troppi giri di parole, la soluzione migliore in questi casi è
rappresentata dall’Inner Class. Il vantaggio nell’utilizzo di essa è la
speciale relazione che una Inner Class ha con una istanza di una Outer Class
(cioè la classe all’interno della quale viene definita l’Inner Class). Una Inner
Class può accedere ai membri di una Outer Class, anche se i membri sono privati.
Il collegamento può avvenire in diversi modi, dipende da come viene
definita l’Inner Class.
Esistono diverse tipologie:
- Inner Classes regolari
- Method-Local Inner Classes
- Anonymous Inner Classes
- Static Inner Classes.
Una Inner Class è regolare quando NON è: Static, Method-local e Anonymous.
Un esempio di codifica è la seguente:
class OuterClass
{
class InnerClass
{
}
}
Se si prova a compilare si ottengono due classi:
OuterClass.class
OuterClass$InnerClass.class
Come si può vedere, una Inner Class è comunque una classe
separata, ma con delle particolarità che vedremo più avanti.
Un esempio di accesso ad un membro della classe esterna:
class ClasseEsterna
{
private int x = 9;
class ClasseInterna
{
public void stampaX()
{
System.out.println("Il valore di
x è "
+ x);
}
}
}
Il codice precedente stamperà il numero 9 poiché, come
abbiamo già detto, l’Inner Class è in grado di accedere ai membri dell’Outer
Class in maniera diretta anche se dichiarati privati.
Istanziare una Inner Class
Il solo modo di accedere ad una Inner Class è attraverso una
istanza della Outer Class che la contiene. Ciò significa che a runtime deve esistere
una istanza di una Outer Class per poter accedere ai membri di una Inner Class.
Distinguiamo due casi:
Caso 1:
Istanziare una Inner Class all’interno di una istanza di una Outer Class
Se l’operazione di creazione di una istanza di una Inner
Class è codificata all’interno di una istanza di una Outer Class il codice è molto
semplice:
class ClasseEsterna {
private int x = 7;
public void creaInnerClass() {
InnerClass in = new InnerClass ();
in.stampaX();
}
class InnerClass {
public void stampaX () {
System.out.println("Il valore di
x è "
+ x);
}
}
}
Tuttavia, è importante tenere presente che la creazione dell’istanza
deve avvenire all’interno di una istanza di una Outer Class. Se si prova a
creare un oggetto di tipo Inner Class all’interno di un metodo statico si
otterrà un errore di compilazione. Il seguente codice non compilerà:
class ClasseEsterna {
private int x = 7;
public static void creaInner() {
InnerClass
in = new InnerClass();
in.stampaX();
}
class InnerClass {
public void stampaX() {
System.out.println("Outer x is " + x);
}
}
}
Caso 2: Creare un
oggetto di una Inner Class all’esterno del codice di una istanza dell’Outer
Class
Come già detto, senza un riferimento ad una istanza di una
outer class, non si possono istanziare oggetti di una Inner Class. Il solo modo
è il seguente:
public static void main(String[] args) {
ClasseEsterna mo = new ClasseEsterna ();
ClasseEsterna.ClasseInterna
inner = mo.new ClasseInterna();
inner.stampaX();
}
In questo caso il nome della Inner Class deve includere il
nome dell’Outer Class e l’operazione deve essere eseguita a partire da un
oggetto di tipo Outer Class.
L’operatore this
A questo punto, vediamo come poter accedere all’Inner o all’Outer
Class all’interno della Inner Class.
- L’operatore fondamentale è il this. Prima di procedere ricordiamo che:
- L’operatore this può essere utilizzato solo all’interno del codice dell’istanza. Esso è inutilizzabile in metodi statici.
- L’operatore this è un riferimento all’oggetto in esecuzione.
- L’operatore this è un modo per un oggetto di passare un riferimento a sé stesso a qualsiasi altro metodo come argomento.
Nel nostro caso, l’operatore this si riferisce all’istanza
della Inner Class. Ma cosa succede se la Inner Class vuole un esplicito
riferimento all’istanza dell’Outer Class? In altre parole, come si può ottenere
un riferimento di tipo “Outer this”? Ritornando al nostro esempio:
class ClasseEsterna {
private int x = 5;
public
void creaInnerClass() {
ClasseInterna in = new ClasseInterna ();
in.stampaX();
}
class ClasseInterna {
public void () {
System.out.println("Il valore di
x è "
+ x);
System.out.println("Il
riferimento della Inner Class " + this);
System.out.println("Il
riferimento della Outer Class " + ClasseEsterna.this);
}
}
}
Generalizzando:
- Per ottenere un riferimento all’istanza dell’Inner Class all’interno di quest’ultima è possibile utilizzare this.
- Per avere un riferimento “outer this” all’interno dell’Inner Class, si utilizza NomedellOuterClass.this
Modificatori
utilizzabili per una Inner Class:
- final
- abstract
- public
- private
- protected
- static – Utilizzato per implementare una Nested Class che vedremo più in avanti
- strictfp
Method-Local
Inner Classes
Abbiamo visto che una Inner Class regolare può essere creata all’interno di un’altra classe e all’esterno di un metodo. Ma si può anche definirla all’interno di un metodo:
class ClasseEsterna
{
private String x = "Classe
Esterna";
void metodoDiTest()
{
class ClasseInterna
{
public void stampaX()
{
System.out.println("Il valore x è
" +
x);
}
}
}
}
Il codice mostrato dichiara una classe ClasseEsterna con un metodo metodoDiTest().
All’interno di quest’ultimo viene dichiarata un’altra classe ClassInterna con all’interno un metodo
chiamato stampaX().
Al codice mostrato manca la parte della creazione
dell’istanza, che deve essere inclusa
nel metodo che definisce l’Inner Class per non ricevere un errore di
compilazione.
class ClasseEsterna
{
private String x = "Classe
Esterna";
void metodoDiTest()
{
class ClasseInterna
{
public void stampaX()
{
System.out.println("Il valore di
x è "
+ x);
}
}
ClasseInterna mi = new ClasseInterna ();
mi.stampaX();
}
}
Una Inner Class Method-Local può essere istanziata solo all’interno
del metodo dove è definita. Nessun altro codice che gira all’interno o
all’esterno dell’Outer Class può istanziare l’Inner Class. Inoltre, la Method-Local
Inner Class condivide una speciale relazione con la classe esterna e può
accedere ai suoi membri privati. Tuttavia, l’oggetto Inner Class non può usare
le variabili locali del metodo in cui è inserita l’Inner Class.
Perché questo limite? Riflettiamo. Le variabili locali di un
metodo vivono nello stack e vengono distrutte al termine del metodo. Tuttavia, dopo il termine dell’esecuzione del
metodo l’oggetto Inner Class può ancora essere “vivo” nell’heap. Per esempio,
un riferimento può essere passato in qualsiasi altra variabile di un'altra
classe. Poiché non può essere assicurata la sopravvivenza delle variabili
locale un oggetto Method-Local Inner Class non può accedere alle variabili
locali. Tutto questo, a meno che la variabile locale sia dichiarata FINAL. Andiamo
a vedere una porzione di codice che non compila:
class ClassEsterna
{
private String x = "Classe
Esterna";
void metodoDiTest()
{
String z = "variabile locale";
class ClasseInterna
{
public void stampaXZ()
{
System.out.println("Il valore di
x è "
+ x);
System.out.println("La variabile
locale è " + z); // Non compila!
}
}
}
}
Per quanto riguarda i modificatori, vale la stessa regola
applicata alla dichiarazione di variabili locali. Una Method-Local Inner Class
non può essere dichiarata public, private, protected, static, transient. Gli
unici due modificatori di rilievo utilizzabili separatamente sono abstract e
final.
Cosa succede se una Method-Local Inner Class è definita
all’interno di un metodo statico?
La Method-Local Class ha accesso ai soli membri static della classe
che la contiene, poiché non ha istanze di oggetti collegate. Infatti, in un
metodo statico non esiste l’operatore this. In altre parole, la Method-Local
Inner Class definita in un metodo statico non può accedere alle variabili di
istanza.
Anonymous
Inner Classes
Un altro tipo di Inner Class molto interessante è
l’Anonymous Inner Class che permette la dichiarazione di una Inner Class senza
specificare il nome. Questo tipo di classe può essere definita sia all’interno
di un metodo che come argomento di un metodo. Esistono due versioni di Anonymous Inner Class:
plain-old e argument-declared. Questa distinzione sarà più Chiara tra
pochissimo.
Plain-Old
Anonymous Inner Classes
Per semplicità suddividiamo la versione Plain-Old in due
casi possibili:
Caso 1:
class A
{
public void stampa()
{
System.out.println("A");
}
}
class B
{
A a = new A()
{
public void stampa()
{
System.out.println("anonymous a");
}
};
}
Nel codice mostrato sono state definite due classi: A e B.
La prima ha un metodo chiamato stampa() e la seconda ha una variabile di istanza di tipo A. Notiamo che la classe B non ha metodi. La
variabile a non si riferisce ad una istanza di A, ma a una istanza di una
sottoclasse Anonima di A.
Dal punto di vista della sintassi ciò che risulta essere
“diverso” dal solito è il termine dell’istruzione di dichiarazione della
variabile a che, piuttosto che terminare per il classico “;” presenta una
parentesi graffa. La classe anonima può essere vista come una sottoclasse di A
che esegue un overriding del metodo stampa(). In genere, l’utilizzo di una Inner
Class anonima è guidato dalla necessità di ridefinire un metodo di una
superclasse in maniera molto veloce. Un aspetto da tenere bene a mente è il
termine della definizione della Inner Class anonima che deve essere sempre il “;”
preceduto dalla chiusura della parentesi graffa.
Ma quali sono le implicazioni riguardo al Polimorfismo?
Si possono richiamare i
metodi sulla Inner Class anonima che sono definiti dal tipo della variabile. Facciamo un esempio:
class Test {
public static void main (String[] args) {
Animale h = new Cavallo();
h.mangia(); // Operazione corretta perchè h è di
tipo Animale e la classe Animale ha il metodo mangia()
h.impenna(); // Operazione non
corretta perchè la classe Animale non ha un metodo impenna()
}
}
class Cavallo extends Animale
{
void impenna() { }
}
class Animale
{
void mangia() { }
}
Caso 2:
La sola differenza tra il caso 1 e il caso 2 è che il caso 1
crea una sottoclasse anonima di una specifica classe mentre il caso 2 crea una
classe anonima che implementa una specifica interfaccia. Vediamo un esempio:
interface A
{
public void stampa();
}
class B
{
A a = new A() {
public void stampa()
{
System.out.println("implementer anonimo");
}
};
}
Il codice mostrato, crea una istanza di una classe anonima
che implementa una interfaccia di tipo A. Questo è il solo caso in cui si può
assistere alla parola chiave new
affiancata dal nome di una interfaccia. Ciò è accettabile in questo caso
poiché in realtà non si sta istanziando un oggetto di tipo A ma si sta creando
una istanza di una classe anonima che implementa l’interfaccia A.
Una cosa da tenere bene a mente è che una classe anonima può
implementare solo una interfaccia.
Argument-Defined
Anonymous Inner Classes
Una volta compreso il meccanismo delle Inner Class anonime
possiamo fare un ulteriore passo avanti. Java offre la possibilità di definire
delle Inner Class anonime come argomenti di chiamate a dei metodi.
Class A
{
void metodoDiTest()
{
B b = new B();
b.do(new In()
{
public void faiQualcosa()
{
System.out.println("fatto");
}
});
}
}
interface In
{
void faiQualcosa();
}
class B
{
void do(In f) { }
}
Nel codice mostrato, stiamo richiamando il metodo do() su un
oggetto B, ma la chiamata al metodo risulta essere più corposa. Questo è dovuto
al fatto che sono due le operazioni che stiamo eseguendo su quell’unica
istruzione: implementazione di una interfaccia e una istanza della classe
ridefinita. Ciò viene eseguito con l’istruzione che parte dal new In() { che inizia la definizione di una nuova classe
anonima che implementa l’interfaccia In. L’istruzione viene terminata dal: “});”
Static
Nested Classes
Una Static Nested Class è una semplice classe implementata
come membro static di una classe esterna.
class ClasseEsterna
{
static class ClasseAnnidata { }
}
Il modificatore static in questo caso significa che la
classe ClasseAnnidata è un membro statico della classe ClasseEsterna.
L’accesso a questa classe è possibile senza avere una istanza della Outer Class.
Istanziare
e usare una Static Nested Class
Nel caso delle Static Nested Class la sitassi per accedere
alla classe interna presenta delle particolarità. Andiamo a vedere un esempio:
class A
{
static class ClasseAnnidata1
{
void stampaQualcosa()
{
System.out.println("stampa
annidata 1");
}
}
}
class B
{
static class ClasseAnnidata2
{
void stampaQualcosa2()
{
System.out.println("stampa
annidata 2");
}
}
public static void main(String[] args)
{
A.ClasseAnnidata1 n = new A.ClasseAnnidata1();
n.stampaQualcosa();
ClasseAnnidata2
b2 = new ClasseAnnidata2();
b2.stampaQualcosa2();
}
Bisogna comunque tenere presente che, in generale, un metodo
statico non ha accesso alle variabili di istanza e ai metodi non-static di una
classe. Ciò vale anche per una Static Nested Class.
0 commenti:
Posta un commento