Classi Annidate in Java
Classi annidate in Java: classi statiche annidate, classi interne, classi locali e classi anonime.
Java permette di dichiarare una classe all'interno di un'altra classe. Il termine generale è classe annidata, e Java ne offre quattro varianti, distinte per il punto in cui si trovano e se mantengono un riferimento a un'istanza che le racchiude:
| Variante | Dove viene dichiarata | Mantiene this esterno? | Capitolo |
|---|---|---|---|
| Classe statica annidata | All'interno del corpo di una classe, con static | No | questo capitolo |
| Classe interna | All'interno del corpo di una classe, senza static | Sì | classi interne |
| Classe locale | All'interno del corpo di un metodo | Sì (se metodo non statico) | classi locali |
| Classe anonima | Una sottoclasse/implementazione creata al volo | Sì (se contesto non statico) | classi anonime |
Il motivo per scegliere una classe annidata rispetto a una classe di primo livello separata è lo scope — la classe annidata è utile solo nel contesto della classe che la racchiude e non dovrebbe essere esposta al resto del codice. Questo capitolo è la panoramica generale; ogni tipo viene trattato più in dettaglio nei capitoli successivi.
Perché annidare le classi?
Tre motivazioni principali:
- Raggruppamento logico. Una
Map.Entryha senso solo all'interno di unaMap. Annidarla rende ovvia questa relazione nel codice. - Incapsulamento. Una classe annidata può essere resa
privatein modo che nulla al di fuori della classe che la racchiude possa farvi riferimento. - Closure sullo stato esterno. Una classe interna / locale / anonima può leggere i campi dell'istanza esterna e le variabili locali del metodo, il che è la base per i gestori di eventi, gli iteratori e molti piccoli pattern adapter.
Se nessuna di queste motivazioni si applica, scrivi una classe di primo livello.
Classi statiche annidate
Una classe contrassegnata con static all'interno di un'altra classe è una classe statica annidata:
public class Outer {
static class Inner {
void hi() { System.out.println("hi"); }
}
}
Outer.Inner i = new Outer.Inner(); // instantiate directly
i.hi();Una classe statica annidata è semplicemente una classe di primo livello che si trova all'interno del namespace di Outer. Non ha un riferimento implicito a un'istanza di Outer — puoi usarla senza mai crearne una. L'unica differenza rispetto a una classe di primo livello è lo scope: Outer.Inner è il nome qualificato.
Questa è la variante che hai già visto nelle Parti 5 e 6 — ogni static class Foo {} in un esempio RunnableJava è una di queste. Abbiamo usato static per poterle istanziare da un main static senza aver bisogno di un'istanza di Outer.
Classi interne (non statiche annidate)
Rimuovi static e ottieni una classe interna. L'istanza della classe interna è legata a un'istanza della classe esterna, mantenendo un riferimento implicito a essa:
public class Outer {
int x = 1;
class Inner { // no static
int get() { return x; } // reads Outer's x through the implicit reference
}
}
Outer o = new Outer();
Outer.Inner i = o.new Inner(); // unusual syntax — bind to o
System.out.println(i.get()); // 1La sintassi insolita o.new Inner() è il modo per creare un'istanza di classe interna legata a una specifica istanza esterna. Le classi interne non possono avere membri static (regola precedente; allentata in Java 16+ per consentire membri static nelle classi interne). Trattato integralmente in classi interne.
Classi locali
Una classe dichiarata all'interno del corpo di un metodo è locale — con scope limitato a quel metodo:
public void run() {
class Step { int n; Step(int n) { this.n = n; } } // visible only in run()
Step s = new Step(5);
}Utile per piccoli helper che non meritano una classe di primo livello o nemmeno una classe annidata a livello di classe. Hanno accesso alle variabili final (o effettivamente final) del metodo che le racchiude. Il capitolo sulle classi locali contiene la storia completa.
Classi anonime
Una classe anonima è una sottoclasse o implementazione di interfaccia monouso definita inline nel punto di utilizzo:
Runnable r = new Runnable() {
@Override
public void run() { System.out.println("hi"); }
};Questa è una singola espressione che (1) definisce una nuova classe che implementa Runnable, (2) crea un'istanza di essa, (3) la assegna a r. La classe non ha nome — esiste solo qui. Quasi tutti i casi d'uso delle classi anonime sono stati sostituiti dalle lambda nel Java moderno, ma sono ancora legali e occasionalmente utili. Vedi classi anonime.
Scegliere il tipo giusto
Un rapido albero decisionale:
- La classe annidata ha bisogno di un riferimento a un'istanza esterna? Se no → classe statica annidata. Se sì → una delle varianti non statiche.
- Viene usata all'interno di un singolo metodo? Se sì → locale o anonima. Se no → interna.
- È una sottoclasse/implementazione di interfaccia monouso con uno o due metodi? Se sì → lambda (preferita) o classe anonima (legacy).
Le classi statiche annidate sono di gran lunga le più comuni. Le classi interne vengono usate per adapter in stile iteratore. Le classi locali e anonime sono più rare nel codice moderno — le lambda coprono la maggior parte dei loro casi d'uso.
Denominazione e accesso
Le classi annidate possono avere qualsiasi modificatore di accesso — public, private, protected, package-private — e le regole dei modificatori sono le stesse dei membri di primo livello. Map.Entry è public; una private static class Node all'interno di un'implementazione di LinkedList è invisibile al mondo esterno.
Le classi annidate compilate ricevono nomi separati da $ nei file .class: Outer$Inner, Outer$1. Li vedrai occasionalmente negli stack trace e nei debugger.
Un esempio pratico
Cosa viene dopo
I tre capitoli successivi approfondiscono ciascuno dei tipi non statici. Per primo: classi interne, il più generale — una classe legata a un'istanza esterna.