MokaByte Numero 29  - Aprile 1999
Corso di Swing
III parte
le inner classes



 

di 
Massimo Carli
Riprende il corso su Swing: prima di entrare nel vivo degli argomenti fondamentali, alcuni concetti di base molto importanti. Questo mese le inner classes

- III parte -


Riprendiamo  il corso sulle Swing che ha subito un rallentamento di un mese a causa dell'uscita dell'atteso JDK1.2 in versione definitiva che d'ora in poi utilizzeremo nei nostri esempi. 
Il cambiamento della versione del JDK annunciata nella prima puntata del corso non porterà comunque a grossi problemi in quanto abbiamo appena accennato ad alcune delle classi presenti in esso. Da questo mese, dopo un piccolo-grande sforzo inizieremo finalmente a vedere le Swing all'opera. L'obiettivo di questa puntata del corso è quello di creare una semplice applicazione costituita da un piccolo JFrame che contiene alcuni bottoni che ci permetteranno di modificarne il L&F. Prima di fare questo introdurremo due concetti fondamentali nella programmazione Swing e non solo:  le Inner Classes, il Modello per Delega (Delegation Model). 
Consiglio al lettore di approfondire molto bene questi argomenti per avere notevoli benefici non solo nell'utilizzo delle Swing ma anche nella programmazione Java in generale. In questa puntata del corso vedremo, inoltre, come l'utilizzo congiunto delle Inner Classes e del Delegation Model, permetta di scrivere codice di buona leggibilità in poco tempo.
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

101: 
 
 
 
 
 
 
 
 
 
 
 
 
 

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:   iec.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 nome_classe.  Nel corpo della classe anonima andranno ridefiniti alcuni metodi della classe nome_classe 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.
 


 
 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it