W3docs

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:

VarianteDove viene dichiarataMantiene this esterno?Capitolo
Classe statica annidataAll'interno del corpo di una classe, con staticNoquesto capitolo
Classe internaAll'interno del corpo di una classe, senza staticclassi interne
Classe localeAll'interno del corpo di un metodoSì (se metodo non statico)classi locali
Classe anonimaUna sottoclasse/implementazione creata al voloSì (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.Entry ha senso solo all'interno di una Map. Annidarla rende ovvia questa relazione nel codice.
  • Incapsulamento. Una classe annidata può essere resa private in 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());           // 1

La 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:

  1. La classe annidata ha bisogno di un riferimento a un'istanza esterna? Se no → classe statica annidata. Se sì → una delle varianti non statiche.
  2. Viene usata all'interno di un singolo metodo? Se sì → locale o anonima. Se no → interna.
  3. È 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

java— editable, runs on the server

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.

Esercitazione

Pratica
Quale tipo di classe annidata NON mantiene un riferimento implicito a un'istanza della classe esterna?
Quale tipo di classe annidata NON mantiene un riferimento implicito a un'istanza della classe esterna?
Was this page helpful?