sabato 8 maggio 2010

Java: classi interne, locali e statiche

Il concetto proposto da Java sulle classi interne è molto semplice: una classe interna è una classe il cui codice risiede all'interno di un'altra classe. Molto spesso si parla di classi annidate. Detto ciò occorre focalizzare l'attenzione su cosa può fare una classe interna in più rispetto alle normali classi.
Una classe interna ha una visibilità ridotta alla sola classe che la contiene, gli oggetti della classe interna, poi, possono accedere ai campi dell'oggetto della classe contenitore. L'uso delle classi interne è particolarmente utile nella definizione di callback (classi anonime) e nella gestione degli eventi. Andiamo per ordine, esistono quattro tipi di classi interne:
  • classe interna di primo livello: la classe interna ha l'identificatore static. Se la classe interna non fa riferimento all'oggetto della classe esterna è possibile dichiarare la classe interna come statica, in questo caso non verrà creato alcun riferimento all'oggetto della classe esterna. Detto in altre parole, una classe interna statica è locale alla classe che la contiene. L'istanza di oggetti appartenenti a classi interne statiche non è legata, allora, agli oggetti della classe contenitore ma alla stessa classe contenitore;
  • classe interna membro: la classe interna non ha l'identificare static (che è solitamente private) e può accedere a tutti i campi della classe contenitore. L'istanza di un oggetto della classe interna è legata all'istanza dell'oggetto della classe contenitore;
  • classe interna locale: la classe interna è locale al metodo che la contiene (e quindi non alla classe o all'oggetto), è cioè una variabile locale del metodo. Può accedere ai campi della classe che la contiene, ad ogni sua variabile, a patto che queste siano di tipo final;
  • classe interna anonima: è una classe locale (a un metodo) che viene istanziata una sola volta. In tal caso, allora, Java permette di creare l'oggetto senza preoccuparsi del nome da dare allo stesso;
Per la JVM le classi interne vengono compilate in file separati, il nome che la JVM assegna a questi lascia comunque intendere le relazioni esistenti fra le classi contenute e quelle contenitore. Se ad esempio la classe A contiene la classe B, la compilazione assegna ad A il file A.class (come solitamente avviene) e a B il file A$B.class.

venerdì 7 maggio 2010

Java: interfacce

Una o più classi che implementano gli stessi metodi possono appartenere alla stessa interfaccia, queste classi forniscono, allora, gli stessi servizi (anche se l'implementazione dei metodi può comunque differire e caratterizzare quindi il servizio). Va subito precisato che un'interfaccia (non quella grafica che pure ho intenzione di spiegare) non è una classe e non permette pertanto l'istanza di nuovi oggetti. Viene invece concesso l'uso dei riferimenti a oggetti che partecipano all'interfaccia.
Detto in altre parole, un'interfaccia elenca i servizi (i metodi) che le classi (quelle che implementano i metodi dell'interfaccia) sono in grado di offrire. Attenzione, l'interfaccia non implementa i metodi che la compongono, si limita a riportarne la firma ed eventualmente qualche costante poi ereditata dalle classi (le costanti di un'interfaccia sono di default di tipo public static final, anche quando ciò non è esplicitamente scritto). Ecco come va descritta un'interfaccia:

public interface Playable {
   int playCard();
   int attack();
   int defend();
}

Tutti i metodi di un'interfaccia sono di default considerati public, anche se alcuni programmatori per abitudine o completezza spesse volte ne riportano per intero l'identificatore. Tutte le classi che intendono partecipare all'interfaccia Playable devono, allora, implementare i metodi playCard(), attack() e defend(). Attenzione a non riportare erroneamente, nelle classi, la firma dei metodi da implementare. Ecco perché per completezza alcuni programmatori scrivono per esteso le firme dei metodi di un'interfaccia (molte volte si copia e incolla il codice e poi si incappa nell'errore, dimenticando che i metodi di un'interfaccia sono sempre pubblici). Per dichiarare la partecipazione di una classe a un'interfaccia si ricorre all'identificatore implements. Ad esempio:


class Creature extends Card implements Playable {
   ...;
}

oppure:

class Land extends Card implements Playable {
   ...
}

Ricordate, l'istruzionePlayable newCreature=new Playable(); è errata! L'istruzione Playable newCreature; è invece lecita! Per permettere il confronto di oggetti, Java ha pensato all'interfaccia Comparable. Tutte le classi che vogliono partecipare all'interfaccia (il servizio è quindi il confronto di oggetti) devono implementare il metodo compareTo() e segnalare dunque la partecipazione, con implements, all'interfaccia stessa.
Perché allora non realizzare la classe astratta Playable, con i metodi astratti playCard(), attack() e defend()? In questo modo le classi Card e Creature potrebbero estendere la classe astratta e fornire un'implementazione dei metodi. Perché non fare così?
La risposta a questa domanda è molto semplice e aiuta a comprendere l'importanza delle interfacce. Java non consente l'ereditarietà multipla, questo significa che una classe può estendere solo una classe! Una classe può invece implementare più interfacce e ciò favorisce la partecipazione della stessa a più servizi, cosa altrimenti impossibile da realizzare se il servizio fosse realizzato con una classe astratta.


public interface Taps {
   boolean isTap();
   boolean tapIt();
}

In base a quanto appena detto, è possibile incontrare codice simile a questo:

class Creature extends Card implements Playable, Taps {
   public int playCard() {
   ...
   }
   public int attack() {
   ...
   }
   public int defend() {
   ...
   }
   public boolean isTap() {
   ...
   }
   public boolean tapIt() {
   ...
   }
}

oppure:

class Land extends Card implements Playable, Taps {
   public int playCard() {
   ...
   }
   public int attack() {
   ...
   }
   public int defend() {
   ...
   }
   public boolean isTap() {
   ...
   }
   public boolean tapIt() {
   ...
   }
}

L'istruzione class Creature extends Card, Playable, Taps non è ammessa da Java, ora siete stati avvisati.

Java: la classe class

Se Java ha pensato alla classe Object (detta anche classe cosmica) per gli oggetti, esisterà qualcosa per modellare le classi? Come fa la JVM a tenere traccia delle classi durante l'esecuzione (a run-time) dell'applicazione?
Java ha così pensato alla classe Class, è attraverso essa che la JVM cerca le informazioni sui metodi e i campi delle classi. La stessa classe Object prevede, infatti, un metodo che restituisce al chiamante un oggetto di tipo Class, si tratta del metodo getClass(), che se invocato su un oggetto ritorna informazioni sulla classe di appartenenza dell'oggetto, un oggetto Class.
Se x è un oggetto della classe Card, la chiamata al metodo x.getClass() ritorna l'oggetto Class che modella la classe Card! E ancora, se siamo interessati al nome della classe è sufficiente invocare sull'oggetto Class il metodo getName(). Quindi, x.getClass().getName() ritorna Card.
Se ci occorre l'istanza di un oggetto avente lo stesso tipo di un altro oggetto a noi noto si può chiamare newIstance(). Pertanto, con x.getClass().newIstance() riceviamo il riferimento a un nuovo oggetto Card (se x è un oggetto della classe Card). Attenzione, il metodo newIstance() della classe Class chiama il costruttore predefinito della classe del parametro implicito (quindi di x). Se siamo interessati alla super classe di un oggetto non ci resta che invocare, allora, il metodo getSuperclass().
Con getFields(), getMethods() e getConstructors() riceviamo un array con i campi dell'istanza e con i metodi pubblici e con i costruttori previsti dalla classe. Con getDeclaredFields(), getDeclaredMethods() e getDeclaredConstructors(), invece, ci vengono restituiti gli array con i campi dell'istanza e i metodi privati (oppure protetti).
Attenzione, gli array ritornati sono di tipo Field per i metodi, di tipo Method per i metodi e di tipo Constructor per i costruttori. Si tratta di tre classi usate dal package java.lang.reflect per modellare i campi, i metodi e i costruttori di una classe. Come sempre, la guida in linea per le API è nostra amica.