CodeGym /Corsi /JAVA 25 SELF /Classi interne (non-static inner)

Classi interne (non-static inner)

JAVA 25 SELF
Livello 16 , Lezione 0
Disponibile

1. Che cos'è una classe interna (inner class)?

In Java una classe può essere dichiarata non solo al livello superiore (nel file), ma anche all'interno di un'altra classe. Una tale classe si chiama annidata (nested class). Se tale classe è dichiarata senza il modificatore static, si chiama interna (ovvero non-static inner class, o semplicemente inner class).

Una classe interna è una classe dichiarata all'interno di un'altra classe ed è associata a un'istanza di tale classe esterna. Può accedere a tutti i campi e metodi della classe esterna, anche se sono private. È come se l'oggetto avesse un «assistente segreto» a cui è permesso tutto!

È un po' come una casa con delle stanze. La casa è la classe esterna, la stanza è la classe interna. La stanza non può esistere senza la casa, ma ha accesso alle sue risorse: luce, riscaldamento, mobili. Se la casa scompare, scompare anche la stanza. Le classi interne in Java funzionano proprio così!

Perché servono le classi interne?

  • Collegamento logico: quando una classe serve solo per lavorare con un'altra classe e non ha senso al di fuori di essa.
  • Incapsulamento: consente di nascondere i dettagli di implementazione senza ingombrare lo spazio dei nomi del package.
  • Accesso ai membri privati: la classe interna può accedere ai campi e ai metodi privati della classe esterna.
  • Compattezza: riduce il numero di classi superflue a livello di package.

Sintassi di dichiarazione di una classe interna

Dichiarare una classe interna è molto semplice: si dichiara all'interno del corpo di un'altra classe, senza il modificatore static.

class Outer {
    // campi e metodi della classe esterna

    class Inner {
        // campi e metodi della classe interna
        void printHello() {
            System.out.println("Hello from Inner!");
        }
    }
}

Qui Inner è una classe interna rispetto a Outer.

Immagine: visualizzazione

Outer
│
├─ campi/metodi
│
└─ Inner (classe interna)
      └─ propri campi/metodi

2. Come creare un'istanza di una classe interna

Qui si nasconde una delle trappole più comuni per i principianti! Un'istanza di una classe interna è sempre associata a un oggetto concreto della classe esterna.

Esempio:

Outer outer = new Outer();             // creiamo un oggetto della classe esterna
Outer.Inner inner = outer.new Inner(); // creiamo la classe interna tramite l'oggetto esterno!
inner.printHello();                    // Hello from Inner!

Provare a fare semplicemente new Inner() porterà a un errore di compilazione, perché Java non sa con quale oggetto della classe esterna associare quella classe interna.

Perché?

La classe interna può accedere ai campi e ai metodi della classe esterna. Per questo deve «sapere» con quale oggetto esterno sta lavorando.

3. Esempio di utilizzo di una classe interna

Vediamo un esempio in cui una classe interna è davvero utile.

Esempio 1: Modello «Zaino e oggetti»

Supponiamo di avere una classe Backpack che può contenere oggetti (Item). Ma vogliamo che la classe Item sia accessibile solo all'interno di Backpack, perché al di fuori dello zaino gli oggetti non ci interessano.

public class Backpack {
    private String owner;

    public Backpack(String owner) {
        this.owner = owner;
    }

    // Classe interna — l'oggetto può esistere solo nello zaino!
    class Item {
        private String name;

        public Item(String name) {
            this.name = name;
        }

        public void printInfo() {
            // Magia! Vediamo il campo private della classe esterna
            System.out.println(owner + " ha l'oggetto: " + name);
        }
    }
}

Utilizzo:

Backpack bp = new Backpack("Vasya");
Backpack.Item item = bp.new Item("Manuale di Java");
item.printInfo(); // Vasya ha l'oggetto: Manuale di Java

Nota: Item può accedere al campo owner, anche se è private!

Esempio 2: Un iteratore per la propria collezione

In Java le collezioni implementano spesso una classe interna-iteratore. Proviamo a creare una nostra semplice collezione con una classe interna che funge da iteratore.

public class IntList {
    private int[] data = new int[10];
    private int size = 0;

    public void add(int value) {
        data[size++] = value;
    }

    // Classe interna — iteratore!
    class Iterator {
        private int index = 0;

        public boolean hasNext() {
            return index < size;
        }

        public int next() {
            return data[index++];
        }
    }
}

Utilizzo:

IntList list = new IntList();
list.add(10);
list.add(20);

IntList.Iterator it = list.new Iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

4. Caratteristiche delle classi interne

Accesso ai membri della classe esterna

La classe interna può accedere a qualsiasi campo o metodo della classe esterna (anche private). È comodo, ma richiede cautela: se la classe esterna cambia, la classe interna potrebbe «rompersi» all'improvviso.

class Outer {
    private int secret = 42;

    class Inner {
        void showSecret() {
            System.out.println("Segreto: " + secret);
        }
    }
}

Una classe interna non può contenere membri statici

Una classe interna (non-static inner) non può contenere campi o metodi statici, tranne le costanti static final (ad esempio, public static final int MY_CONST = 123;). Questo perché una classe interna è sempre «collegata» a un'istanza della classe esterna.

Se è necessario dichiarare una classe annidata statica, usate il modificatore static (ne parleremo nella prossima lezione).

Visibilità di una classe interna

Una classe interna può essere private, protected, public oppure avere visibilità a livello di package (package-private).

public class Outer {
    private class Inner { /* ... */ }
}

5. Dettagli utili

Quando usare le classi interne

Le classi interne non sono per bellezza, ma per una logica organizzazione del codice. Usatele quando:

  • La classe serve solo in un punto (ad es., un iteratore di supporto, un handler, parte di una struttura complessa).
  • La classe è strettamente collegata alla classe esterna e non ha senso al di fuori di essa.
  • Volete nascondere i dettagli di implementazione ad altre classi del package.

Non usate le classi interne solo per moda! A volte è meglio portare la classe al livello superiore se può essere utile altrove.

Classe interna e this

All'interno di una classe interna ci si può riferire ai campi e ai metodi della classe esterna usando OuterClassName.this.

class Car {
    private String model = "Tesla";

    class Engine {
        void printModel() {
            // Ci riferiamo esplicitamente all'oggetto esterno
            System.out.println("Modello: " + Car.this.model);
        }
    }
}

Questa sintassi è particolarmente utile se la classe interna e quella esterna hanno campi con gli stessi nomi.

Quando NON conviene usare le classi interne

Non usatele se:

  • La classe interna non accede ai campi/metodi della classe esterna
  • La classe può essere utile in altre parti del programma
  • La classe esterna diventa troppo grande e complessa

In questi casi è meglio:

  • Rendere la classe annidata statica (static class)
  • Spostare la classe in un file separato
  • Usare metodi normali invece di un'intera classe

6. Errori tipici nell'uso delle classi interne

Errore n. 1: tentare di creare una classe interna senza un oggetto della classe esterna.
Se si scrive new Inner(), il compilatore mostrerà un errore: "No enclosing instance of type Outer is accessible". Create sempre la classe interna tramite l'oggetto della classe esterna: outer.new Inner().

Errore n. 2: tentare di dichiarare campi o metodi statici in una classe interna.
Una classe interna non può contenere membri statici (tranne le costanti). Se serve, usate una classe annidata statica, cioè dichiarate la classe con il modificatore static.

Errore n. 3: uso eccessivo delle classi interne.
Se la classe interna non utilizza i campi/metodi della classe esterna, probabilmente dovrebbe essere static o addirittura estratta all'esterno. Non create classi interne «tanto per».

Errore n. 4: confusione nell'accesso ai campi della classe esterna.
Se la classe interna e quella esterna hanno campi con gli stessi nomi, utilizzate OuterClassName.this.field per riferirvi esplicitamente al campo della classe esterna.

1
Compito
JAVA 25 SELF, livello 16, lezione 0
Bloccato
Gestione magazzino: etichettatura delle scatole 📦
Gestione magazzino: etichettatura delle scatole 📦
1
Compito
JAVA 25 SELF, livello 16, lezione 0
Bloccato
Assistente personale: saluto cortese 🗣️
Assistente personale: saluto cortese 🗣️
1
Compito
JAVA 25 SELF, livello 16, lezione 0
Bloccato
Biblioteca magica: catalogazione dei rotoli 📜
Biblioteca magica: catalogazione dei rotoli 📜
1
Compito
JAVA 25 SELF, livello 16, lezione 0
Bloccato
Casa intelligente: indirizzamento delle stanze 🏠
Casa intelligente: indirizzamento delle stanze 🏠
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION