MokaByte
Numero 12 - Ottobre 1997
|
|||
|
|
||
Daniela Ruggeri |
Una valida alternativa alle Classi Top-Level | ||
Fino alla
versione del JDK 1.0.2. Java supportava solo le classi ad alto livello
(top-level classes), classi cioè create esplicitamente e che entravano
a far parte dei packages. Vediamo ora come funzionano le Inner Class messe
a disposizione a partire dal JDK 1.1.
Introduzione
Come lavorano le Inner Classes?
Tipi di Inner Class
Perché le Inner Class?
Conclusione
Nella versione 1.1, sono state rese disponibili le cosiddette Inner Class o classi interne definite internamente ad altre classi o metodi di altri classi entro un blocco di istruzioni o anonimamente entro un'espressione. Per contrapposizione le classi top-level vengono chiamate Outer Class.
Queste classi sono invisibili all'esterno e sono utilizzabili solo all'interno della classe che le include.
Le Inner classes nascono dalla combinazione di un blocco strutturato di istruzioni e la programmazione basata sulle classi. La programmazione in questo modo diventa più ricca e flessibile, permettendo allo sviluppatore Java di gestire singoli blocchi di istruzione interni alla classe top-level, che si comportano come veri e propri oggetti istanze di classi.
In questo paragrafo presenteremo esempi di vari tipi di Inner Class. Presenteremo dunque le Inner Class interne alle classi top-level, quelle interne a metodi di classi top-level e le classi anonime.
Esempio di Inner Class interna ad una classe:Per quanto riguarda le applicazioni, se si fa riferimento alle Inner Class nel metodo main( ), bisogna prima di tutto istanziare l'applicazione e poi riferirsi alle inner class come parte dell'applicazione.
// Outer Classpublic class MiaClasse {
..... istruzioni
MiaInnerClass classeinterna = new MiaInnerClass();
..... istruzioni// Inner Class
class MiaInnerClass extends AltraClasse {
MiaInnerClass () {
....istruzioni
} // fine costruttore
} // fine Inner Class
} // Fine Outer Class
Esempio:
public class MiaApplicazione {Notare l'uso di new associato alla classe outer per istanziare la classe inner.// Inner Class
class MiaInnerClass extends AltraClasse {
MiaInnerClass () {
....istruzioni
} // fine costruttore
}public static void main(String args[]) {
MiaApplicazione app = new MiaApplicazione;
MiaApplicazione.MiaInnerClass inner = MiaApplicazione.new MiaInnerClass();
}
}
Esiste anche la possibilità di definire Inner Class statiche, in modo che non sia più necessario istanziare la classe outer per vederle.
Una classe anonima è una notazione abbreviata per creare un semplice oggetto locale in-line" entro ogni espressione, semplicemente inglobando il codice richiesto in una espressione "new".
Esempio di classe anonima:
UnaClasseAnonima UnMetodo(parametro) {Una classe può essere nidificata entro un metodo o entro un qualsiasi altro spazio (per esempio entro una IF).
return new UnaClasseAnonima() { // inizio specifiche per la classe anonimapublic boolean UnMetodoDiClasseAnonima() {
... istruzioni ...
}... istruzioni ...
}; // fine specifiche per la classe anonima. Il carattere ";" è obbligatorio.
}
1. Definite entro un metodo
// Outer Class2. Definite entro uno spazio di un metodo
public class MiaClasse {
..... istruzioni
UnInterfacciaEsistente UIE = MioMetodoOuter(); // i metodi definiti in MiaInnerClass si rendono disponibili
..... istruzioniUnInterfacciaEsistente MioMetodoOuter() {
// Inner Class
class MiaInnerClass implements UnInterfacciaEsistente {
..... istruzioni
}
return new MiaInnerClass();
}} // Fine Outer Class
Per esempio:if (condizione)
{
// Inner Class
class MiaInnerClass { // inizio definizione inner class
.... variabili ...
// costruttore
MiaInnerClass (tipo parametro) {
id = s;
}
// definizione di un metodo
UnTipo UnMetodo() {
... istruzioni ...
}
... altri metodi ...
} // fine definizione Inner Class
MiaInnerClass mic = new MiaInnerClass(parametro); // Istanziazione della Inner Class
UnTipo ut = mic.UnMetodo(); // utilizzo di un metodo della Inner Class
}
Interessante è anche la possibilità di "creare un oggetto di una classe anonima che erediti da un'altra Classe".
Consideriamo l'esempio:abstract class ClasseEsterna {
abstract public int UnMetodo();
}
public class ClassePrincipale {
... istruzioni ...
// metodo che restituisce il tipo ClasseEsterna
public ClasseEsterna cont() {
return new ClasseEsterna() { // inizio spazio dedicato alla classe anonima
private int i = 11;
public int UnMetodo() {
return i;
}
}; // fine spazio dedicato alla classe anonima. Il carattere ";" è obbligatorio
}
}
Come
lavorano le Inner Classes?
Il codice delle Inner Class è strettamente relativo all'istanza di classe che la include, quindi l'istanza di inner class ha bisogno per lavorare di poter determinare l'istanza della classe che la include.
Per raggiungere questo scopo, quando il sorgente viene compilato con il compilatore JavaSoft Java 1.1, questo prima di generare il bytecode lo trasforma in codice java compatibile con le specifiche Java 1.0 in cui non erano previste le Inner Class.
Consideriamo il seguente esempio:
import java.awt.*;Questo semplice esempio consiste in un cerchio che cambia colore quando ci si clicca sopra.
import java.awt.event.*;
public class RoundButton extends Canvas {
boolean pressed = false;
int width;
int height;
int x;
int y;public RoundButton (){
this(10,10,150,150);
}public RoundButton (int x, int y, int width, int height){
if (width < 0) {
throw new IllegalArgumentException ("Larghezza illegale:" + width);
}
if (height < 0) {
throw new IllegalArgumentException ("Altezza illegale:" + height);
}
if (x < 0) {
throw new IllegalArgumentException ("Coordinata x illegale:" + x);
}
if (y < 0) {
throw new IllegalArgumentException ("Coordinata y illegale:" + y);
}
this.x = x;
this.y = y;
this.width = width;
this.height = height;
addMouseListener (new ClickAdapter()); // Creazione istanza Inner Class
}public void paint (Graphics g) {
super.paint(g);
if (isEnabled()) {
if (pressed)
g.setColor (Color.red);
else
g.setColor (Color.black);
} else {
g.setColor (Color.gray);
}
g.fillOval(x,y,width,height);
}// Definizione Inner Class
private final class ClickAdapter extends MouseAdapter {
public void mousePressed (MouseEvent e) {
if (isEnabled()) {
pressed = true;
repaint();
}
}public void mouseReleased (MouseEvent e) {
if (isEnabled()) {
pressed = false;
repaint();
}
}
}
}
I due stati (nel caso specifico nero e rosso) sono mostrati in figura 1. e 2.
Si è scelta una classe Adapter (classe in genere astratta che implementa una EventListener e raccoglie tutti i metodi della stessa; questa classe riceve gli eventi del tipo di listener specificato, e la sua utilità deriva dalla maggiore facilità di creare ascoltatori semplicemente estendendo la EventListener richiesta e ridefininendo i metodi di interesse) come Inner Class, perché risulta piuttosto semplice gestire gli eventi relativi ad un'interfaccia estensione di EventListener mediante la codifica di una classe interna.
Si acclude anche un esempio di applet che fa uso di questa classe componente:
public class BottRound extends Applet {Compilando il componente ci accorgiamo che vengono create due classi:
RoundButton t;
Button button1;
public void init() {
setLayout(null);
addNotify(); resize(426,266);
t = new RoundButton (5,5,150,150);
t.setBounds(101,79,500,300); add(t);
}
}
Quindi se il nome qualificato della Inner Class è RoundButton.ClickAdapter, il nome della classe è RoundButton$ClickAdapter
Passiamo ora a considerare il codice trasformato prima della generazione del bytecode.
La classe RoundButton diventa:
/* Originally compiled from RoundButton.java */Qui la prima cosa che notiamo è la trasformazione del parametro entro il metodo addMouseListener in new RoundButton$ClickAdapter(this). In particolare notiamo il passaggio come parametro della classe includente al costruttore di RoundButton$ClickAdapter.import java.awt.*;
public synchronized class RoundButton extends Canvas
{
boolean pressed;
int width;
int height;
int x;
int y;public RoundButton()
{
this(10, 10, 150, 150);
}public RoundButton(int i1, int j, int k, int i2)
{
pressed = false;
if (k < 0)
throw new IllegalArgumentException(new StringBuffer("Larghezza illegale:").append(k).toString());
if (i2 < 0)
throw new IllegalArgumentException(new StringBuffer("Altezza illegale:").append(i2).toString());
if (i1 < 0)
throw new IllegalArgumentException(new StringBuffer("Coordinata x illegale:").append(i1).toString());
if (j < 0)
throw new IllegalArgumentException(new StringBuffer("Coordinata y illegale:").append(j).toString());
x = i1;
y = j;
width = k;
height = i2;
addMouseListener(new RoundButton$ClickAdapter(this));
}public void paint(Graphics g)
{
super.paint(g);
if (!isEnabled())
g.setColor(Color.gray);
else if (pressed)
g.setColor(Color.red);
else
g.setColor(Color.black);
g.fillOval(x, y, width, height);
}
}
La classe RoundButton$ClickAdapter diventa:
/* Originally compiled from RoundButton.java */Notiamo anche la dichiarazione final e il modificatore synchronized. Infatti se una variabile locale o parametro in una classe si riferisce ad un'altra classe essa deve essere dichiarata final. A causa dei potenziali problemi di sincronizzazione, non vi è alcun modo per due oggetti di poter condividere i cambiamenti di una variabile locale.import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;final synchronized class RoundButton$ClickAdapter extends MouseAdapter
{
private final RoundButton this$0;public void mousePressed(MouseEvent mouseEvent)
{
if (this$0.isEnabled())
{
this$0.pressed = true;
this$0.repaint();
}
}public void mouseReleased(MouseEvent mouseEvent)
{
if (this$0.isEnabled())
{
this$0.pressed = false;
this$0.repaint();
}
}RoundButton$ClickAdapter(RoundButton roundButton)
{
this$0 = roundButton;
}
}
Qui invece notiamo:
e dell'assegnazione
nel costruttore
Permette
alla Inner Class di accedere a tutte le variabili (this$0.pressed)
e di utilizzare tutti i metodi (this$0.isEnabled() e this$0.repaint())
della classe che la includere.
Notiamo anche
la dichiarazione final. Infatti se una variabile locale o parametro
in una classe si riferisce ad un'altra classe essa deve essere dichiarata
final. A causa dei potenziali problemi di sincronizzazione, non vi è
alcun modo per due oggetti di poter condividere i cambiamenti di una variabile
locale.
In genere i programmatori sono assolutamenti inconsapevoli dell'avvenuta trasformazione.
Fin dall'inizio della progettazione del linguaggio Java i suoi disegnatori hanno riconosciuto la necessità di avere una sorta di "metodo puntatore" che connettesse pezzi di codice differenti. Questo comporta la gestione di un individuale blocco di codice che può essere usato senza puntamento all'oggetto o classe contenente il codice. In altri linguaggi come C o Lisp le function pointers assolvono questo compito. Per esempio questi puntatori spesso servono a connettere un ritorno o evento di un modulo a un pezzo di codice di un altro modulo.
Secondo lo stile dei linguaggi orientati agli oggetti, Smalltalk ha dei blocchi che sono grossi pezzi di codice che si comportano come piccoli oggetti.
Come con le function pointers di C o Lisp, i blocchi Smalltalk possono essere usati per organizzare dei complessi flussi di controllo.
In Java, gli stessi complessi flussi di controllo inclusi la gestione degli eventi e le iterazioni, sono espressi da classi e interfacce. Java usa le interfacce in un modo simile alle function types di altri linguaggi.
Il programmatore Java crea le equivalenti di un ritorno o di un blocco Smalltalk racchiudendo il codice previsto in una classe adapter che implementa l'interfaccia richiesta.
Con le inner classes, l'uso degli adapters è semplice quanto i blocchi Smalltalk, o le Inner functions negli altri linguaggi. Quindi poiché le classi sono più ricche delle funzioni (perché hanno più entry points), gli oggetti adapter Java sono più potenti e più strutturati delle function pointers.
Così, laddove i programmatori C, Lisp, e Smalltalk usano varianti dei "metodi puntatori"per incapsulare porzioni di codice, i programmatori Java usano gli oggetti.
Dove altri linguaggi hanno function types specializzate e notations per incapsulare il comportamento come funzioni, Java ha solo tipi di classi ed interfacce.
In Java, "la classe è il quanto del comportamento". Uno dei benefici di questo approccio è la semplicità e stabilità per la JVM, che non ha bisogno di speciali supporti per le inner classes o funzioni puntatori.
Senza le inner classes, i programmatori Java possono creare ritorni e iterazioni mediante le classi adapter definite ad alto livello, ma le specifiche possono essere goffe e impraticabili. Mediante le inner classes invece, i programmatori Java possono scrivere concise classi adapter che sono codificate precisamente dove sono necessarie, e operare direttamente sulle variabili interne e i metodi di una classe o blocco. Da cui, le inner classes fabbricano classi adapter pratiche secondo lo stile della codifica.
Conclusione
Spero che questo
articolo abbia contribuito a chiarire un po' le idee sull'uso di tecniche
alternative al fine di rendere più efficienti e manutenibili i propri
programmi. Prevediamo che in futuro, le inner classes saranno ancora più
efficienti delle loro equivalenti classi adapter ad alto livello, a causa
della notevole possibilità nell'aumento nell'ottimizzazione di queste
classi inaccessibili dall'esterno. Questo avverrà probabilmente
con l'introduzione della nuova versione del JDK 1.2.
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|