Classi anonime in Java

1. Introduzione

In questo tutorial, prenderemo in considerazione le classi anonime in Java.

Descriveremo come possiamo dichiararli e crearne istanze. Discuteremo anche brevemente delle loro proprietà e limitazioni.

2. Dichiarazione di classe anonima

Le classi anonime sono classi interne senza nome. Poiché non hanno nome, non possiamo usarli per creare istanze di classi anonime. Di conseguenza, dobbiamo dichiarare e istanziare classi anonime in una singola espressione nel punto di utilizzo.

Possiamo estendere una classe esistente o implementare un'interfaccia.

2.1. Estendi una classe

Quando istanziamo una classe anonima da una esistente, usiamo la seguente sintassi:

Tra parentesi specifichiamo i parametri che sono richiesti dal costruttore della classe che stiamo estendendo:

new Book("Design Patterns") { @Override public String description() { return "Famous GoF book."; } }

Naturalmente, se il costruttore della classe genitore non accetta argomenti, dovremmo lasciare vuote le parentesi.

2.2. Implementa un'interfaccia

Possiamo anche istanziare una classe anonima da un'interfaccia:

Ovviamente, le interfacce di Java non hanno costruttori, quindi le parentesi rimangono sempre vuote. Questo è l'unico modo in cui dovremmo farlo per implementare i metodi dell'interfaccia:

new Runnable() { @Override public void run() { ... } }

Una volta che abbiamo istanziato una classe anonima, possiamo assegnare quell'istanza a una variabile per poterla fare riferimento in un secondo momento.

Possiamo farlo usando la sintassi standard per le espressioni Java:

Runnable action = new Runnable() { @Override public void run() { ... } };

Come già accennato, una dichiarazione di classe anonima è un'espressione, quindi deve essere una parte di un'istruzione . Questo spiega perché abbiamo messo un punto e virgola alla fine dell'istruzione.

Ovviamente, possiamo evitare di assegnare l'istanza a una variabile se creiamo quell'istanza inline:

List actions = new ArrayList(); actions.add(new Runnable() { @Override public void run() { ... } });

Dovremmo usare questa sintassi con molta attenzione in quanto potrebbe facilmente subire la leggibilità del codice, specialmente quando l'implementazione del metodo run () richiede molto spazio.

3. Proprietà anonime della classe

Ci sono alcune particolarità nell'utilizzo di classi anonime rispetto alle normali classi di primo livello. Qui tocchiamo brevemente le questioni più pratiche. Per informazioni più precise e aggiornate, possiamo sempre consultare le specifiche del linguaggio Java.

3.1. Costruttore

La sintassi delle classi anonime non ci permette di farle implementare più interfacce. Durante la costruzione, potrebbe esistere esattamente un'istanza di una classe anonima . Pertanto, non possono mai essere astratte. Poiché non hanno nome, non possiamo estenderli. Per lo stesso motivo, le classi anonime non possono avere costruttori dichiarati in modo esplicito.

Infatti l'assenza di un costruttore non rappresenta per noi alcun problema per i seguenti motivi:

  1. creiamo istanze di classi anonime nello stesso momento in cui le dichiariamo
  2. da istanze di classi anonime, possiamo accedere alle variabili locali e racchiudere i membri della classe

3.2. Membri statici

Le classi anonime non possono avere membri statici ad eccezione di quelli che sono costanti.

Ad esempio, questo non verrà compilato:

new Runnable() { static final int x = 0; static int y = 0; // compilation error! @Override public void run() {...} };

Invece, otterremo il seguente errore:

The field y cannot be declared static in a non-static inner type, unless initialized with a constant expression

3.3. Ambito delle variabili

Le classi anonime catturano le variabili locali che si trovano nell'ambito del blocco in cui abbiamo dichiarato la classe:

int count = 1; Runnable action = new Runnable() { @Override public void run() { System.out.println("Runnable with captured variables: " + count); } }; 

Come si vede, le variabili locali count e action sono definite nello stesso blocco. Per questo motivo, possiamo accedere a count dall'interno della dichiarazione della classe.

Si noti che per poter utilizzare le variabili locali, devono essere effettivamente definitive. A partire da JDK 8, non è più necessario dichiarare le variabili con la parola chiave final . Tuttavia, quelle variabili devono essere definitive . Altrimenti, otteniamo un errore di compilazione:

[ERROR] local variables referenced from an inner class must be final or effectively final

Affinché il compilatore decida che una variabile è, di fatto, immutabile, nel codice, dovrebbe esserci un solo punto in cui assegnarle un valore. Potremmo trovare ulteriori informazioni sulle variabili finali effettive nel nostro articolo "Perché le variabili locali utilizzate nei Lambda devono essere definitive o effettivamente finali?"

Diciamo solo che, come ogni classe interna, una classe anonima può accedere a tutti i membri della sua classe che lo racchiude .

4. Casi d'uso di classi anonime

Potrebbe esserci una grande varietà di applicazioni di classi anonime. Esploriamo alcuni possibili casi d'uso.

4.1. Gerarchia di classi e incapsulamento

We should use inner classes in general use cases and anonymous ones in very specific ones in order to achieve a cleaner hierarchy of classes in our application. When using inner classes, we may achieve a finer encapsulation of the enclosing class's data. If we define the inner class functionality in a top-level class, then the enclosing class should have public or package visibility of some of its members. Naturally, there are situations when it is not very appreciated or even accepted.

4.2. Cleaner Project Structure

We usually use anonymous classes when we have to modify on the fly the implementation of methods of some classes. In this case, we can avoid adding new *.java files to the project in order to define top-level classes. This is especially true if that top-level class would be used just one time.

4.3. UI Event Listeners

In applications with a graphical interface, the most common use case of anonymous classes is to create various event listeners. For example, in the following snippet:

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ... } }

we create an instance of an anonymous class that implements interface ActionListener. Its actionPerformed method gets triggered when a user clicks the button.

Since Java 8, lambda expressions seem to be a more preferred way though.

5. General Picture

Le classi anonime che abbiamo considerato sopra sono solo un caso particolare di classi annidate. In genere, una classe annidata è una classe dichiarata all'interno di un'altra classe o interfaccia :

Guardando il diagramma, vediamo che le classi anonime insieme a quelle dei membri locali e non statici formano le cosiddette classi interne . Insieme alle classi membri statiche , formano le classi nidificate.

6. Conclusione

In questo articolo, abbiamo considerato vari aspetti delle classi anonime Java. Abbiamo anche descritto una gerarchia generale di classi annidate.

Come sempre, il codice completo è disponibile nel nostro repository GitHub.