Le Inner Classes
Le Inner Classes (Classi Interne) sono state introdotte dal JDK1.1 e rappresentano un insieme di nuove regole per la scrittura di classi ed interfacce. Nella scrittura del codice ci si è resi conto della mancanza di uno strumento che permettesse la creazione di determinate classi con visibilità limitata a quella di una classe iniziale, che condividesse con essa alcuni dati o che semplicemente fosse legata alla prima da un insieme di considerazioni logiche. Ci si è accorti che sarebbe stato molto utile poter definire una classe all'interno di un'altra in modo da condividerne tutte le proprietà senza dover creare classi con costruttori o altri metodi set enormi con grandissima possibilità di errore. Il problema si traduceva, quindi, nella realizzazione di un particolare compilatore che incontrate queste classi interne (Inner Classes), le traducesse in un modo comprensibile all'interprete. Quest'ultimo, infatti, non chiede altro che disporre di tanti file .class quante sono le classi con cui deve avere a che fare. Da queste considerazioni si evince che le Inner Classes sono una nuova potenzialità del compilatore e che, almeno in questo contesto, non si hanno problemi di compatibilità con le altre versioni della JVM.
Possiamo classificare le Inner Classes in quattro gruppi diversi:
Classi ed interfacce interne top-level
Questa categoria di classi interne è rappresentata da un insieme di classi ed interfacce che non hanno caratteristiche differenti dalle classi che le contengono se non per il fatto di essere legate ad esse da una relazione logica di convenienza come può essere, ad esempio, quella di appartenere ad uno stesso package o di riguardare uno stesso aspetto della programmazione (I/O, networking, ecc). Queste classi interne sono anche dette statiche in quanto ci si riferisce ad esse utilizzando il nome della classe che le contiene allo stesso modo di una proprietà statica e la relativa classe. Per comprendere meglio questo tipo di Inner Classes esaminiamo nel dettaglio l'esempio dato dal Listato3_1. Ogni riga del listato è stata numerata in modo da semplificarne la spiegazione.
1: package corso.swing.capitolo3;
2:
3: /**
4: *Questa classe permette di
descrivere le Inner Classes
5: *del tipo Top-Level dette
anche static per come si
6: *definiscono e per come si
indicano.
7: *L'esempio rappresenta una
contenitore di oggetti di tipo
8: *Item ovvero che implementano
questa interfaccia. Viene
9: *definita anche una classe
di nome SimpleItem che rappresenta
10: *la più semplice implementazione
dell'interfaccia Item.
11: *Vengono fornite anche, come
Inner Class le eccezioni che
12: *vengono sollevate quando
si cerca di estrarre da un
13: *contenitore vuoto o si tenta
di inserire un Item in un
14: *contenitore pieno
15: *
16: *
17: *@author Massimo Carli
18: *@version 0.1 (28/12/1998)
19: */
20:
21: public class ItemContainer
{
22:
23: /**
24: *Questa interfaccia
interna permette di definire gli
25: *oggetti che possono
essere contenuti in questo
26: *oggetto Contenitore.
Essi devono fornire le informazioni
27: *relative ad una chiave
ed ad un valore entrambi di tipo
28: *String. Essendo una
Interfaccia non serve indicarla
29: *come statica.
30: */
31: public interface Item
{
32:
33: /**
34: *La chiave
35: */
36: public String
getKey();
37:
38: /**
39: *Il valore
40: */
41: public String
getValue();
42:
43: /*
44: *Ritorna una
istanza della particolare
45: *implementazione
dell'interfaccia
46: *@param key
Chiave
47: *@param value
Valore
48: */
49: public Item getInstance(String
key, String value);
50:
51: }// fine interfaccia
Item
52:
53: /**
54: *Questa classe è
la più semplice implementazione della
55: *interfaccia Item.
Essa è definita come static in quanto
56: *vogliamo creare una
classe Top-Level
57: */
58: public static class
SimpleItem implements Item {
59:
60: /* La chiave
*/
61: private
String key;
62:
63: /* Il valore
*/
64: private
String value;
65:
66: /**
67: *Costruttore
di default
68: */
69: public SimpleItem(){
70: this("","");
71: }// fine
72:
73: /**
74: *Costruttore
completo
75: *@param
key Chiave
76: *@param
value Valore
77: */
78: public SimpleItem(String
key, String value){
79: this.key=key;
80: this.value=value;
81: }// fine
82:
83: /**
84: *La chiave
85: */
86: public String
getKey(){
87: return
key;
88: }// fine
89:
90: /**
91: *Il valore
92: */
93: public String
getValue(){
94: return
value;
95: }// fine
96:
97: /*
98: *Ritorna una
istanza della particolare
99: *implementazione
dell'interfaccia
100: *@param key
Chiave
101: *@param value
Valore
102: */
103: public Item
getInstance(String key, String value){
104: return
new SimpleItem(key,value);
105: }
// fine
106:
107: }
// fine classe SimpleItem
108:
109: /**
110: *Eccezione che viene
sollevata quando si tenta un
111: *inserimento ed il
contenitore è pieno.
112: */
113: public static class
FullItemContainerException extends Exception{
114: public FullItemContainerException(){
super();
}
115: public FullItemContainerException(String
message){
super(message);
}
116: }
// fine FullItemContainerException
117:
118: /**
119: *Eccezione che viene
sollevata quando si tenta una
120: *estrazione ed il
contenitore è vuoto.
121: */
122: public static class
EmptyItemContainerException extends Exception{
123: public EmptyItemContainerException(){
super();
}
124: public EmptyItemContainerException(String
message){
super(message);
}
125: }
// fine EmptyItemContainerException
126:
127: /*
128: *Dimensione massima
del contenitore
129: */
130: protected final
static int MAXDIM=10;
131:
132: /*
133: *Tipo dell'oggetto
Item contenuto nell'ItemContainer.
134: */
135: protected Item
factory;
136:
137: /*
138: *Array che contiene
gli Item
139: */
140: protected Item[]
items;
141:
142: /*
143: *Dimensione de contenitore
144: */
145: protected int
dim;
146:
147: /*
148: *Oggetti contenuti
nel contenitore
149: */
150: protected int
size;
151:
152: /**
153: *Costruttore di default
154: */
155: public ItemContainer(){
156: this(MAXDIM);
157: }
// fine
158:
159: /**
160: *Crea un contenitore
di dimensione massima <EM>size</EM>
161: *e che utilizza un
<EM>SimpleItem</EM> come factory
162: *delle Item
163: *@param dim
Dimensione massima del contenitore
164: */
165: public ItemContainer(int
dim){
166: this(dim,new
SimpleItem());
167: }
// fine
168:
169: /**
170: *Crea un contenitore
di dimensione massima <EM>size</EM>
171: *e che utilizza builder
come factory delle Item
172: *@param dim
Dimensione massima del contenitore
173: *@param factory Factory
degli Item
174: */
175: public ItemContainer(int
dim,Item factory){
176: this.dim=dim;
// Dimensione massima
177: this.factory=factory;
// Factory degli Item
178: items= new Item[size];
// Contenitore
179: size=0;
// Inizialmente vuoto
180: }
// fine
181:
182: /**
183: *Permette di inserire
un Item
184: *@param key
Chiave
185: *@param value
Valore
186: */
187: public void addItem(String
key, String value)
188: throws FullItemContainerException
{
189: if(size==dim)
throw new FullItemContainerException();
190: items[size++]=factory.getInstance(key,value);
191: }
// fine
192:
193: /**
194: *Prende l'ultimo Item
inserito
195: */
196: public Item removeItem()
197: throws EmptyItemContainerException
{
198: if(size==0)
throw new EmptyItemContainerException();
199: return items[--size];
200: }// fine
201:
202: }// fine classe ItemContainer
L'esempio rappresenta la realizzazione di un contenitore di un certo tipo di oggetti Item le cui caratteristiche sono descritte dalla omonima interfaccia. Tali oggetti devono possedere una chiave ed un valore entrambi di tipo String (ovviamente si tratta di un esempio e non si fanno considerazioni sulla effettiva utilità di questa classe anche se si noteranno le analogie con uno stack). L'interfaccia Item è legata alla realizzazione della classe ItemContainer per cui sarebbe utile un procedimento che le legasse in qualche modo. Analoga considerazione si può fare per la classe SimpleItem che rappresenta la più semplice implementazione dell'interfaccia Item. Nel JDK1.02 avremmo potuto organizzare queste classi ed interfacce mettendole tutte in uno stesso package. Esiste, però, anche una gerarchia logica tra la classe ItemContainer e le altre. Le Inner Classes ci permettono di definire l'interfaccia Item e la classe SimpleItem all'interno della definizione della classe ItemContainer e di riferirci ad esse come ItemContainer.Item ed ItemContainer.SimpleItem. La stessa cosa può essere fatta per le eventuali eccezioni sollevate da alcuni metodi. Nel Listato 3_1 notiamo la definizione dell'interfaccia Item alla riga 31. Notiamo come essa sia definita come una qualunque altra interfaccia solo che è all'interno della definizione di una classe. Alla riga 58 inizia la definizione della classe SimpleItem che implementa l'interfaccia Item definita precedentemente. E' possibile riferirsi all'interfaccia semplicemente con il suo nome in quanto siamo all'interno della classe SimpleItem. In caso contrario avremmo dovuto riferirci ad essa come ItemContainer.Item. Nella definizione della classe interna notiamo anche l'utilizzo del modificatore static. Questo modificatore permette di indicare che la classe SimpleItem è da considerarsi una classe come le altre solamente che per riferirsi ad essa bisogna passare per la classe ItemContainer. Il legame tra le classi è quindi solamente logico ed una istruzione del tipo:
ItemContainer.SimpleItem item = new ItemContainer.SimpleItem();
è perfettamente lecita. Nel caso dell'interfaccia non è necessario inserire il modificatore static in quanto non ha senso creare una istanza di una interfaccia. Ogni interfaccia è quindi sempre statica. Le considerazioni fatte per la classe SimpleItem valgono, ovviamente, anche per le eccezioni FullItemContainerException e EmptyItemContainerException introdotte qui per completezza.
E' bene sottolineare come l'utilizzo di questa tipologia di classi interne sia utile solamente per creare ordine logico tra un certo insieme di classi ed interfacce. Queste classi sono dette anche statiche in quanto non fanno riferimento, a differenza della classi membro, ad una particolare istanza della classe che le contiene.
Il listato 3.2 rappresenta un esempio di come tali classi possano essere utilizzate..
1: package corso.swing.capitolo3;
2:
3:
4: /**
5: *Verifica della classe <EM>corso.swing.capitolo3.ItemContainer</EM>
6: *
7: *@author Massimo Carli
8: *@version 0.1 (28/12/1998)
9: */
10:
11:
12: public class ProvaItemContainer {
13:
14:
15: /*
16: *Main
17: */
18: public static void main(String str[]){
19: ItemContainer container=new ItemContainer();
20: try{
21: container.addItem("1","uno");
22: container.addItem("2","due");
23: container.addItem("3","tre");
24: }
catch(ItemContainer.FullItemContainerException e){
25: System.out.println("Errore: "+e.getMessage());
26: }
// fine
try/catch
27:
28: try{
29: /*
30: * Se proviamo ad estrarre subito un oggetto di tipo Item,
31: * ovvero che implementa l'omonima interfaccia, nel modo
32: * che segue (commentato) si avrebbe un errore in quanto
33: * l'inteprete non riesce a trovare tale tipo.
34: */
35:
36: //Item item=container.removeItem();
// Errato
37:
38: /*
39: * Il modo seguente è quello corrretto ovvero l'interfaccia
40: * Item e' dichiarata come "contenuta" nella classe
41: * ItemContainer e viene indicata come suo membro statico.
42: * Questo e' il motivo per cui le classi interne di questo
43: * tipo sono dette STATICHE
44: */
45:
46: ItemContainer.Item item=container.removeItem();
// Esatto
47:
48: /*
49: * Se non si vuole definire l'interfaccia Item nel modo
50: * visto, sarebbe sufficiente importarla nel seguente
51: * modo:
52: *
53: * import corso.swing.capitolo3.ItemContainer.*;
54: *
55: * ovvero valgono le stesse regole relative alla
56: * organizzazione di classi ed interfaccie in package.
57: * Considerazione analoga per le eccezioni.
58: */
59:
60: System.out.println("key : "+item.getKey()+" value= "+item.getValue());
61:
62: }
catch(ItemContainer.EmptyItemContainerException e){
63: System.out.println("Errore: "+e.getMessage());
64: }
// fine
try/catch
65: }// fine
66:
67:
68:
69: }// fine classe ProvaItemContainer
Abbiamo detto che la possibilità di creare classi innestate una dentro l'altra doveva essere gestita dal compilatore. Se compiliamo la classe ItemContainer notiamo, oltre alla creazione del file ItemContainer.class, la creazione dei file ItemContainer$Item.class, ItemContainer$SimpleItem.class, ItemContainer$EmptyItemContainerException e ItemContainer$FullItemContainerException. Notiamo inoltre che questi file sono contenuti nella stessa directory per cui non si ha, nella organizzazione fisica delle classi, un procedimento analogo a quello dei package. Il compilatore ha eseguito quella che si chiama source-code transformation inserendo il simbolo $ per indicare che una classe è contenuta in un'altra.ù
Se diamo un'occhiata alle nostre Swing notiamo la presenza di alcune classi di questo tipo. Un esempio è dato dalla classe javax.swing.JInternalFrame.JDesktopIcon.
Altra considerazione riguarda la convenzione utilizzata nei nomi delle classi e dei package. Notiamo che solitamente i package hanno nomi minuscoli mentre le classi hanno nomi con iniziali maiuscole. Questa semplice convenzione ci permette di distinguere quella che è una classe interna top-level da quella che è una semplice classe appartenente ad un package
Classi membro
Le classi top-level descritte in precedenza sono dette anche statiche in quanto non sono legate ad una particolare istanza della classe che le contiene ma da un legame logico. Le Member Classes (Classi Membro) sono definite allo stesso modo delle classi top-level con l'importante differenza di non essere definite con il modificatore static. Questo ha conseguenza importantissime. Ogni istanza di una Member Classes è associata ad una particolare istanza della classe che le contiene. Inoltre, ogni classe membro può accedere a qualunque proprietà o metodo (anche definiti private), della classe, o della gerarchia di classi, che la contengono. Non solo. Anche le classi che la contengono possono accedere a proprietà e metodi delle classi membro (in questo caso assume importanza il modificatore di acceso utilizzato nella definizione della classe interna). Ecco che le classi membro vengono utilizzate come classi di supporto che devono accedere ad un certo insieme di informazioni della classe che le contiene. Come esempio supponiamo di aggiungere alla classe ItemContainer il metodo elements() che fornisce una Enumeration dei dati in esso contenuti. Ricordiamo che per Enumeration intendiamo una qualunque classe che implementa l'interfaccia java.util.Enumeration. Se avessimo voluto procedere nel modo classico avremmo creato una nuova classe ItemEnumerator contenuta in un omonimo file. Questa classe avrebbe avuto un costruttore, o un metodo set, per ricevere il riferimento all'ItemContainer (o semplicemente alla sua proprietà items) necessario per accedere agli Item e creare l'Enumeration. Questo, come detto, ci obbligherebbe alla creazione di un nuovo file, di una nuova classe ma, soprattutto, a decidere come fornire alla nuova classe il riferimento all'ItemContainer. Nel nostro caso la cosa non sarebbe difficile ma non sono rari i casi in cui questo procedimento può portare a facili errori. Nel Listato 3_3 diamo la soluzione utilizzando le Member Classes.
1: package corso.swing.capitolo3;
2:
3: import java.util.*;
4:
5: /**
6: *Questa classe aggiunge il metodo <EM>elements()</EM> che permette
7: *di ottenere una Enumeration degli elementi contenuti in
8: *un <EM>ItemContainer</EM>.
9: *
10: *@author Massimo Carli
11: *@version 0.1 (03/01/1999)
12: */
13:
14: public class EnumeratedItemContainer extends ItemContainer{
15:
16: /**
17: * In questo punto definiamo la classe membro
18: *<EM>ItemEnumerator</EM> che ha visibilità su
19: * tutte le proprietà della classe <EM> ItemContainer </EM>
20: */
21: private class ItemEnumerator implements Enumeration {
22:
23: /*
24: *Posizione dell'elemento corrente nell'array
25: *<EM> items</EM>
26: */
27: private int indice_corrente;
28:
29: /**
30: *Costruttore: notiamo che non necessita di
31: *alcun parametro in quanto vede tutte le
32: *proprietà della classe che la contiene.
33: */
34: public ItemEnumerator(){
35: indice_corrente=0;
36: }// fine
37:
38: /**
39: *Primo metodo dell'interfaccia <EM>Enumeration</EM>
40: *Indica se esistono elementi
41: */
42: public boolean hasMoreElements(){
43: return (indice_corrente<size);
44: }// fine
45:
46:
47: /**
48: *Secondo metodo dell'interfaccia <EM>Enumeration</EM>
49: *Ritorna l'elemento corrente
50: */
51: public Object nextElement(){
52: if(indice_corrente>size)
53: throw new NoSuchElementException("Item Finiti");
54: Object obj=items[indice_corrente++];
55: return obj;
56: }// fine
57: }// fine classe membro
58:
59:
60: /**
61: *Costruttore di default
62: */
63: public EnumeratedItemContainer(){
64: super();
65: }// fine
66:
67: /**
68: *Crea un contenitore di dimensione massima <EM>size</EM>
69: *e che utilizza un <EM>SimpleItem</EM> come factory
70: *delle Item
71: *@param dim Dimensione massima del contenitore
72: */
73: public EnumeratedItemContainer(int dim){
74: super(dim);
75: }// fine
76:
77: /**
78: *Crea un contenitore di dimensione massima <EM>size</EM>
79: *e che utilizza builder come factory delle Item
80: *@param dim Dimensione massima del contenitore
81: *@param factory Factory degli Item
82: */
83: public EnumeratedItemContainer(int dim,Item factory){
84: super(dim,factory);
85: }// fine
86:
87:
88: /**
89: *Ritorna una <EM>Enumeration</EM> degli item
90: */
91: public Enumeration elements(){
92: /**
93: *Creiamo una instanza della classe <EM>ItemEnumerator</EM>
94: *la quale utilizza le proprietà ed i valori della particolare
95: *instanza della classe <EM>ItemContainer</EM> cui essa appartiene.
96: */
97: return new ItemEnumerator();
98: }// fine
99:
100: }//fine classe
La nuova classe EnumeratedItemContainer si ottiene estendendo la classe ItemContainer e aggiungendo il metodo elements() alla riga 91. Notiamo come esso non faccia altro che ritornare una istanza della classe ItemEnumerator. Il costruttore invocato non ha nessun parametro. La definizione della classe ItemEnumerator è alla linea 21. Notiamo che non compare il modificatore static per cui la classe definita è una classe membro. Essa ha accesso a tutte le proprietà della classe in cui è contenuta. Questo è evidente alla riga 54 in cui, all'interno della classe ItemEnumerator, accediamo alla proprietà items della classe in cui è contenuta. Il valore di tale proprietà è quello relativo ad una particolare istanza della classe EnumeratedItemContainer ovvero a quella si cui si invoca il metodo elements().
1: package corso.swing.capitolo3;
2:
3: import java.util.*;
4:
5: /**
6: *Verifica della classe
<EM>corso.swing.capitolo3.EnumeratedItemContainer</EM>
7: *
8: *@author Massimo Carli
9: *@version 0.1 (02/01/1999)
10: */
11:
12:
13: public class ProvaEnumeratedItemContainer {
14:
15:
16: /*
17: *Main
18: */
19: public static void main(String str[]){
20: EnumeratedItemContainer container=new EnumeratedItemContainer();
21: try{
22: container.addItem("1","uno");
23: container.addItem("2","due");
24: container.addItem("3","tre");
25: }
catch(ItemContainer.FullItemContainerException e){
26: System.out.println("Errore: "+e.getMessage());
27: }
// fine
try/catch
28: Enumeration enum= container.elements();
29: while(enum.hasMoreElements()){
30: ItemContainer.Item item=(ItemContainer.Item)(enum.nextElement());
31: System.out.println("key:"+item.getKey()+"value="+item.getValue());
32: }
// fine
while
33: }// fine main
34:
35:
36:
37: }// fine classe ProvaItemContainer
Il Listato3_4 è un esempio di utilizzo della classe EnumeratedItemContainer. In tale esempio si richiama semplicemente il metodo elements() sulla istanza container della classe EnumeratedItemContainer (riga 28). Gli elementi della Enumeration saranno quelli contenuti nella particolare istanza container.
Abbiamo parlato di classi membro ma non di interfacce membro. Questa affermazione è ovvia se si pensa che una interfaccia deve semplicemente elencare un insieme di firme di metodi è non può, in nessun modo, fare riferimento a particolari istanze di una classe. Analoga considerazione varrà anche per i tipi di inner classes che vedremo di seguito.
Il compilatore, in questo tipo di inner classes, deve effettuare una source-code transformation leggermente più complicata di quella relativa ad una classe top-level. Abbiamo accennato a quello che avremmo fatto utilizzando il JDK1.02. In pratica, il compilatore esegue il lavoro per noi, creando una classe che ha un costruttore che prevede il passaggio delle proprietà della classe contenitore, visibili alla classe interna, alla classe interna stessa. Il risultato è analogo al caso precedente e, per ogni classe membro, viene creato un file .class che ha il nome composto dal nome della classe che la contiene e dal nome della classe stessa divisi dal simbolo $. Nel nostro esempio si ha la classe EnumeratedItemContainer$ItemEnumerator.class del nostro package.
Supponiamo ora il caso in cui nella classe membro ItemEnumerator, avessimo definito una proprietà items dello stesso tipo e nome della proprietà della classe contenuta (Listato 3_5).
1:
2:
3: - - - -
4:
5: /**
6: * In questo punto definiamo la classe membro
7: *<EM>ItemEnumerator</EM> che ha visibilità su
8: * tutte le proprietà della classe <EM> ItemContainer </EM>
9: */
10: private class ItemEnumerator implements Enumeration {
11:
12:
13: /*
14: *Array che contiene gli Item
15: */
16: protected Item[] items;
17:
18:
19: /**
20: *Costruttore: notiamo che non necessita di
21: *alcun parametro in quanto vede tutte le
22: *proprietà della classe che la contiene.
23: */
24: public ItemEnumerator(){
25:
26: /*
27: *1) Questo metodo e' errato perche' this.items e items sono
28: * la stessa cosa in questo metodo.
29: */
30:
31: this.items=items;
// NO
32:
33: /*
34: *2) Questo procedimento permette di riferirsi ad una
35: * proprietà specificando il tipo della istanza a
36: * cui appartiene
37: */
38:
39: this.items= EnumeratedItemContainer.this.items;
40: }// fine
41:
42:
43:
44: - - - -
All'interno del costruttore della ItemEnumerator avremmo potuto assegnare il valore della proprietà items della classe contenitore alla omonima proprietà della classe interna. Ma come facciamo a distinguere le due proprietà items? Il metodo classico sarebbe quello indicato nella riga 31. In questo caso si avrebbero dei problemi in quanto items e this.items indicano la stessa proprietà in quanto siano interni alla definizione della classe ItemEnumerator. Dal JDK1.1 è stata allora introdotta la notazione nomeclasse.this per indicare in modo univoco, la classe a cui la parola chiave this si riferisce. Nel nostro caso con EnumeratedItemContainer.this.items indichiamo la proprietà items della classe EnumeratedItemContainer. Questo metodo funziona anche se la classe EnumeratedItemContainer non fosse la classe che contiene ItemEnumerator direttamente, ma attraverso più livelli.
Supponiamo ora il caso in cui la classe interna ItemEnumerator abbia una visibilità almeno package per cui possa essere vista anche da altre classi oltre a quelle che la contengono. Se volessimo creare una istanza di tale classe all'esterno della classe EnumeratedItemContainer, ci sarebbe il problema di indicare quale istanza della classe considerare. Dal JDK1.1 è stata introdotta la seguente notazione
istanza.new Classe();
per creare una istanza della classe interna Classe riferendoci alla istanza istanza della classe contenitore. Nel nostro caso avremmo dovuto agire come nel Listato3_6 in cui, alla riga 22, creiamo l'istanza della classe ItemEnumerator riferita alla particolare istanza container della classe EnumeratedItemContainer.
1:
2:
3: - - - -
4:
5:
6: /**
7: *Main di prova
8: */
9: public static void main(String[] str){
10:
11: - - - -
12: /*
13: *Creiamo una istanza della classe EnumeratedItemContainer
14: */
15: EnumeratedItemContainer container=new EnumeratedItemContainer();
16:
17:
18: /*
19: * Creiamo un ItemEnumerator che utilizzerà i dati della istanza
20: * appena creata.
21: */
22: Enumeration enum=container.new ItemEnumerator();
23:
24: - - - -
25:
26: }// fine main
27:
28:
29:
30: - - - -
Una considerazione analoga può essere fatta per la parola chiave super. Da un punto di vista puramente teorico potrebbe capitare il caso in cui una classe top-level derivi da una classe membro. Questo significa che una sottoclasse che non è contenuta in nessun'altra classe, ha una superclasse che lo è. Nel caso in cui nel costruttore della classe derivata si richiamasse il costruttore della superclasse, attraverso super(), bisognerà specificare rispetto a quel istanza tale costruttore deve riferirsi. In tale caso si utilizza una notazione analoga a quella del this ovvero quella del Listato3_7.
1: package corso.swing.capitolo3;
2:
3:
4:
5: /**
6: * Esempio di una classe top-level che estende una classe interna.
7: *
8: *@author Massimo Carli
9: *@version 0.1 (03/01/1999)
10: */
11:
12: public class ClasseTopLevel extends
EnumeratedItemContainer.ItemEnumerator {
13:
14:
15: /**
16: *Costruttore di default.
17: */
18: public ClasseTopLevel(EnumeratedItemContainer eic){
19: eic.super();
20: }// fine
21:
22: - - -
23:
24: }// fine classe
Alla riga 18 vi è la definizione del costruttore che possiede un parametro di tipo EnumeratedItemContainer che corrisponde alla particolare istanza di cui richiamare il costruttore con super(). Nel Listato3_7 notiamo che anche una classe membro viene indicata con la notazione utilizzata per le classi top-level. Nel nostro caso si ha, infatti, la classe EnumeratedItemContainer.ItemEnumerator.
Esistono due limitazione relativamente a questo tipo di classi interne. La prima riguarda il fatto che le varie classi innestate non devono avere lo stesso nome. La seconda, a questo punto abbastanza ovvia, dice che una classe membro non può essere statica.
Classi locali
Con le classi locali (Local Classes) iniziamo finalmente la trattazione delle classi che si utilizzano maggiormente nelle Swing per la gestione degli eventi con il metodo per delega (Delegation Model). Le classi locali, a differenza delle classi membro a cui assomigliano, hanno la caratteristica di essere definite all'interno di un blocco di codice Java. Esse hanno visibilità legata a quella del blocco Java (ovvero parte compresa tra { e } ) che le contengono. Questo porta ad una importante conseguenza: le classi locali possono accedere, oltre ai metodi e proprietà delle classi che la contengono, anche alle sole proprietà final definite nello stesso blocco. Questo è causa della source-code transformation operata dal compilatore. Quando viene creata una istanza di una classe locale, i valori delle variabili visibili in tale scopo, vengono copiate in altrettante variabili della inner classes. Per assicurare la coerenza tra i valori definiti nello scopo in cui è definita la classe locale e quello interni alla classe stessa, l'unico modo era quello di considerare solamente le proprietà final. La definizione di una proprietà final in un determinato blocco di codice è stata introdotta nel JDK 1.1. proprio per questo aspetto. Le limitazioni della classi locali sono quelle di non poter utilizzare proprietà static e di non poter essere dichiarate come public, protected, private o static. Quest'ultima considerazione è forse la principale distinzione tra una classe interna ed una classe locale.
Come semplice esempio di questo tipo di classe interna estendiamo la classe EnumeratedItemContainer aggiungendo il metodo getExtremes() che ritorna un oggetto di tipo ExtremeItems, che dispone semplicemente di due proprietà first e last che contengono gli Item estremi.
1: package corso.swing.capitolo3;
2:
3: import java.util.*;
4:
5: /**
6: *Questa classe estende <EM>EnumeratedItemContainer</EM> e
7: *mostra un semplice utilizzo delle classi Locali.
8: *
9: *@author Massimo Carli
10: *@version 0.1 (24/01/1999)
11: */
12:
13: public class EnumeratedItemContainerWithLocal
14: extends EnumeratedItemContainer{
15:
16:
17:
18:
19: /**
20: *Questa interfaccia è necessaria per poter indicare il tipo
21: *di ritorno della classe locale ExtremeItems.
22: */
23: public interface ExtremeInterface {
24:
25: /**
26: *Ritorna il primo Item
27: */
28: public Item getFirst();
29:
30: /**
31: *Ritorna l'ultimo Item
32: */
33: public Item getLast();
34:
35: }// fine interfacciainterna
36:
37:
38:
39: /**
40: *Costruttore di default. Definiamo solo questo
41: *per motivi di spazio.
42: */
43: public EnumeratedItemContainerWithLocal(){
44: super();
45: }// fine
46:
47:
48: /**
49: *Ritorna una Enumeration che contiene solamente il primo
50: *e l'ultimo Item, ovviamente se presenti
51: */
52: public ExtremeInterface getExtremes(){
53:
54: /*
55: *Classe interna che ritorna un oggetto che contiene gli oggetti
56: *estremi contenuti
57: *
58: */
59: class ExtremeItems implements ExtremeInterface{
60:
61: /*
62: *Indice del primo
63: */
64: public Item first;
65:
66: /*
67: *Indice dell'ultimo elemento
68: */
69: public Item last;
70:
71:
72: /**
73: *Costruttore: notiamo che non necessita di
74: *alcun parametro in quanto vede tutte le
75: *proprietà della classe che la contiene.
76: */
77: public ExtremeItems(){
78: if(size==0)
79: first=last=null;
80: else {
81: first=items[0];
82: last=items[size-1];
83: }// fine else
84: }// fine
85:
86:
87:
88: /**
89: *Ritorna il primo elemento
90: */
91: public Item getFirst(){
92: return first;
93: }// fine metodo
94:
95: /**
96: *Ritorna l'ultimo elemento
97: */
98: public Item getLast(){
99: return last;
100: }// fine metodo
101:
102:
103: }// fine definizione classe interna
104:
105: return new ExtremeItems();
106: }// fine metodo
107:
108:
109:
110:
111:
112: }// fine classe EnumeratedItemContainerWithLocal
Alla riga 23 abbiamo dichiarato una interfaccia che ci permette di indicare il tipo dell'oggetto di ritorno del metodo getExtremes().Non avremmo potuto, infatti, indicare come tipo dell'oggetto di ritorno quello dichiarato all'interno del metodo stesso. Alla riga 52 inizia la definizione del metodo getExtremes(). All'interno del corpo del metodo vi è quindi la definizione della classe ExtremeItems. Notiamo come l'intestazione della classe non presenti alcun modificatore di accesso. Tale classe, infatti, ha visibilità e vita legati solamente a quelli dello scopo in cui è dichiarata ed un modificatore di accesso non avrebbe senso. Tutte le proprietà utilizzate dalla classe locale sono visibili al di fuori dello scopo in cui essa è definita per cui non ci sono problemi legati all'utilizzo del modificatore final. Sarà il compilatore che si preoccuperà di passare, attraverso il costruttore, un riferimento a tali proprietà. Nel caso in cui ci fossero state delle variabili definite all'interno del metodo getExtremes(), esse avrebbero dovuto essere definite final.
Se compiliamo la classe EnumeratedItemContainerWithLocal notiamo la presenza del file EnumeratedItemContainerWithLocal$ExtremeInterface.class relativo all'interfaccia top-level definita alla riga 23 ed un altro file di nome EnumeratedItemContainerWithLocal$1$ExtremeItems.class. Notiamo l'aggiunta di un numero. Le classi locali sono infatti definite all'interno di un particolare scopo ed esistono sono all'interno dello stesso. Nulla ci vieterebbe, quindi, di definire la stessa classe locale in punti diversi (ed in scopi diversi) di una stessa classe. Il numero presente nel nome del file .class è quindi relativo ai diversi punti in cui una stessa classe locale viene definita.
Classi anonime
L'ultimo tipo di inner classes che esaminiamo è quello delle classi anonime o anonymous classes. Esse si possono tranquillamente considerare delle classi locali prive di nome e, a differenza di queste, vengono definite in una espressione invece che in uno statement Java. Dalla versione JDK1.1 è stata introdotta una nuova sintassi per l'utilizzo di questo tipo di classe. Nel caso in cui si volesse creare una istanza anonima di una classe nome_classe basterà scrivere:
new nome_classe ([lista-parametri]) {
// corpo della classe
}
Con questa sintassi si crea una istanza di una classe anonima che estende la classe di nome nomeclasse. Nel corpo della classe anonima andranno ridefiniti alcuni metodi della classe nomeclasse implicitamente estesa, o definiti nuovi metodi di utilità. Siccome una classe anonima non ha nome si presenta il problema della creazione di un costruttore. Esistono quindi due possibilità.
Si utilizza la lista dei parametri della classe anonima. Essi verranno implicitamente passati ad un costruttore della classe nome_classe compatibile, ovvero di stessa firma.
Ad esempio, nel caso in cui volessimo estendere la classe Panel utilizzando il costruttore che prevede il passaggio del Layout, e ridefinendo la paint(), avremmo scritto:
new Panel(nuovo_layout) {
public paint(Graphics g) {
// nuovo corpo della paint
} // fine paint
} // fine anonymous class
Si utilizza un'altra nuova funzionalità introdotta nel JDK1.1 chiamata instance initializer. Esso non è altro che un blocco{} inserito nel corpo della classe. Esso verrà eseguito alla creazione di ogni istanza della classe stessa.
Per risolvere lo stesso problema con questo metodo avremmo scritto:
new Panel() {
// Instance initializer
{
setLayout(nuovo_layout);
}
public paint(Graphics g) {
// nuovo corpo della paint
} // fine paint
} // fine anonymous class
Per le anonymous classes esiste anche la seguente nuova sintassi:
new nome_interfaccia() {
// corpo della classe
}
In questo caso nome_interfaccia è una interfaccia ed il corpo della classe ne deve rappresentare una implementazione. Nel corpo della classe dovranno essere implementati, quindi, tutti i metodi della interfaccia dichiarata dopo il new. Nel caso di una Enumeration basterebbe scrivere:
new Enumeration() {
public boolean hasMoreElements() {
// implementazione
} // fine
public Object nextElement() {
// implementazione
} // fine
} // fine
L'utilizzo delle classi anonime è molto semplice e velocizza di molto la scrittura del codice. Ovviamente il suo utilizzo è consigliato quando si ha a che fare con implementazioni e classi molto piccole quali quelle di gestione degli eventi che vedremo in seguito.
Le limitazione relative alle classi anonime sono le stesse di quelle descritte per le classi locali.
Ultima osservazione riguarda la source code transformation. Anche in questo caso il compilatore creerà una classe di nome legato a quello della classe che la contiene. Questa volta, essendo la classe priva di nome, il nome del file .class conterrà solamente un numero progressivo indicativo dell'ordine della classe anonima nel codice. Se una classe di nome MiaClasse dispone di una classe anonima, questa produrrà un file di nome MiaClasse$1.class.
Nei prossimi articoli vedremo moltissimi esempi che utilizzano classi anonime.
