W3docs

Classi Interne in Java

Definisci classi interne non statiche in Java che contengono un riferimento implicito a un'istanza della classe contenitore.

Una classe interna è una classe annidata non statica — dichiarata all'interno di un'altra classe senza il modificatore static. La caratteristica fondamentale: ogni istanza di classe interna è legata a un'istanza della classe contenitore e porta un riferimento implicito ad essa. Dall'interno della classe interna, puoi leggere e scrivere i campi dell'istanza esterna e chiamare i suoi metodi come se fossero tuoi.

Questo rende le classi interne lo strumento giusto quando una classe piccola e secondaria deve partecipare intimamente allo stato di un'altra classe — il caso più noto sono gli iteratori che scorrono i dati interni del loro contenitore.

Questa pagina illustra come dichiarare una classe interna, come crearne una (occorre sempre un'istanza esterna), il qualificatore Outer.this, il caso d'uso canonico degli iteratori, il rischio di memory leak che deriva dal riferimento implicito, e come scegliere tra una classe interna e una classe annidata static.

Dichiarare una classe interna

Basta omettere static dalla dichiarazione di una classe annidata:

public class Outer {
  private int x = 1;

  class Inner {                       // no static — inner class
    int get() { return x; }            // reads Outer's x directly
  }
}

Inner non ha campi propri eppure get() restituisce 1. Il semplice x viene risolto come Outer.this.x attraverso il riferimento implicito.

Creare un'istanza

Poiché ogni istanza di classe interna è legata a quella esterna, occorre un'istanza esterna per crearne una interna. Ci sono due modi:

Outer       o = new Outer();
Outer.Inner i = o.new Inner();         // bind explicitly to o

…oppure dall'interno di un metodo non statico di Outer:

public class Outer {
  void demo() {
    Inner i = new Inner();             // implicitly bound to this
  }
}

La sintassi o.new Inner() è rara e sorprendente — la maggior parte del codice crea istanze di classe interna dall'interno dei metodi della stessa classe esterna, dove il legame è implicito.

Outer.this — superare una collisione di nomi

Quando la classe interna dichiara un campo con lo stesso nome di uno nella classe esterna, quello interno lo oscura. Per accedere a quello esterno, bisogna qualificarlo con Outer.this:

public class Outer {
  int x = 1;
  class Inner {
    int x = 2;
    void demo() {
      System.out.println(x);             // 2  — Inner's x
      System.out.println(this.x);        // 2  — Inner's x
      System.out.println(Outer.this.x);  // 1  — Outer's x
    }
  }
}

this in una classe interna fa riferimento all'istanza interna; Outer.this fa riferimento all'istanza esterna contenitrice.

Il caso d'uso canonico — gli iteratori

Il motivo classico per usare una classe interna è implementare un iteratore sugli elementi privati di un contenitore:

public class IntList {
  private int[] data;
  private int   size;

  // ... constructors, add, ...

  public Iterator<Integer> iterator() {
    return new InnerIterator();
  }

  private class InnerIterator implements Iterator<Integer> {
    private int i = 0;
    public boolean hasNext()  { return i < size; }
    public Integer next()     { return data[i++]; }
  }
}

InnerIterator accede a data e size direttamente tramite il riferimento implicito all'istanza esterna. Non servono setter né accessor — la classe interna fa parte dell'implementazione di IntList.

Si noti private class InnerIterator. Dall'esterno, chi chiama vede l'interfaccia pubblica Iterator<Integer>; non sa che InnerIterator esiste. Questo è il vantaggio di incapsulamento offerto dall'annidamento.

Le classi interne mantengono un riferimento — e tengono in vita l'istanza esterna

Un pericolo sottile. Finché un'istanza di classe interna è raggiungibile, la JVM non può effettuare il garbage collection dell'istanza esterna a cui è legata. Restituire un'istanza di classe interna a codice con lunga durata di vita (ad esempio installando un listener da qualche parte) può tenere interi grafi di oggetti in vita più a lungo del previsto.

public class Window {
  Listener installListener() {
    return new Listener();           // returned to whoever calls this
  }
  class Listener { ... }              // holds a Window reference forever
}

Se installListener() viene memorizzato in un registro statico, la Window resta in vita finché il registro non viene svuotato. La soluzione è di solito rendere la classe annidata static e passare esplicitamente i dati necessari, spezzando il riferimento implicito.

Questo è il motivo principale per cui molti team usano le classi annidate static per impostazione predefinita e ricorrono alle classi interne solo quando hanno specificamente bisogno del legame.

Membri statici nelle classi interne

Per gran parte della storia di Java, le classi interne non potevano dichiarare membri static (campi, metodi o classi annidate static). Java 16 ha allentato questa restrizione — le classi interne possono ora avere membri statici. Tuttavia, se ci si trova a volerli, questo è spesso un segnale che la classe dovrebbe essere static essa stessa.

static vs interna — la scelta

Una regola utile: rendi la classe static a meno che tu non abbia effettivamente bisogno del riferimento esterno.

  • Classe annidata statica: più semplice, più leggera, non blocca l'istanza esterna in vita.
  • Classe interna: comodo zucchero sintattico per accedere a outerInstance.field quando la relazione è genuina.

Se l'unica cosa che fai è Outer.this.field, prendi semplicemente un Outer come parametro del costruttore e rendi la classe statica.

Un esempio pratico

java— editable, runs on the server

Cosa viene dopo

Il prossimo tipo di classe annidata è la versione inline e monouso: le classi anonime, usate per situazioni in cui si vuole creare una sottoclasse e istanziarla in un'unica espressione. Continua con le classi anonime.

Esercizio

Pratica
Perché molti team usano per default una classe annidata statica anche quando una classe interna non statica funzionerebbe?
Perché molti team usano per default una classe annidata statica anche quando una classe interna non statica funzionerebbe?
Was this page helpful?