CodeGym /Corsi /JAVA 25 SELF /Classi locali: dichiarazione all'interno dei metodi

Classi locali: dichiarazione all'interno dei metodi

JAVA 25 SELF
Livello 16 , Lezione 3
Disponibile

1. Classe locale

Una classe locale è una classe dichiarata all’interno di un metodo (o persino all’interno di un blocco di codice, ad esempio in un ciclo o in un blocco if). È visibile e accessibile solo all’interno di quel metodo e, al di fuori di esso, è come se non esistesse. A differenza delle classi anonime, una classe locale ha un nome (anche semplice), può contenere più metodi e persino campi.

Se facciamo un’analogia — immagina di preparare un piatto complesso seguendo una ricetta. In uno dei passaggi devi preparare una salsa speciale che si usa solo in quella ricetta e da nessun’altra parte. Invece di pubblicare un libro di ricette a parte per una sola salsa, ne descrivi la preparazione direttamente all’interno della ricetta principale. La classe locale funziona allo stesso modo — è una «ricetta dentro la ricetta», necessaria solo in uno specifico metodo.

Esempio di dichiarazione di una classe locale

public class Outer {
    void someMethod() {
        class Local {
            void print() {
                System.out.println("Hello from Local!");
            }
        }
        Local local = new Local();
        local.print();
    }
}

Qui la classe Local è dichiarata all’interno del metodo someMethod della classe Outer. Esiste solo in questo metodo e non è visibile dall’esterno.

Sintassi delle classi locali

La dichiarazione di una classe locale è simile alla dichiarazione di una classe normale, solo che avviene all’interno di un metodo:

void myMethod() {
    class MyLocalClass {
        void doSomething() {
            System.out.println("La classe locale funziona!");
        }
    }

    MyLocalClass obj = new MyLocalClass();
    obj.doSomething();
}

Particolarità della sintassi:

  • Una classe locale può essere dichiarata all’interno di qualsiasi metodo, costruttore o anche di un blocco di inizializzazione.
  • Il nome della classe locale è obbligatorio (a differenza delle classi anonime).
  • Una classe locale non può essere dichiarata con modificatori di accesso (public, private, protected) né con il modificatore static.
  • Una classe locale può implementare interfacce o estendere altre classi.

Confronto tra tipi di classi:

public class Example {
// 1. Classe normale (in un file separato)
// class RegularClass { ... }

    // 2. Classe interna (dentro la classe)
    class InnerClass { }
    
    // 3. Classe annidata statica (dentro la classe)
    static class NestedClass { }
    
    void method() {
        // 4. Classe locale (nel metodo)
        class LocalClass { }
        
        // 5. Classe anonima (nel metodo, senza nome)
        Object obj = new Object() { };
    }
}

Classe locale = una classe con nome solo per questo metodo!

2. Caratteristiche delle classi locali

Accesso alle variabili del metodo

Una classe locale può accedere a:

  • Ai campi della classe esterna (anche se sono private).
  • Solo alle variabili final o effectively final del metodo circostante.

Che cos’è «effectively final»?
È una variabile il cui valore non cambia dopo l’inizializzazione. Ad esempio:

void foo() {
    int x = 5; // effectively final
    class L { void print() { System.out.println(x); } }
}

Se in seguito scrivessi x = 10;, il compilatore segnalerebbe un errore.

Perché?

Ciò dipende da come Java implementa le classi locali «sotto il cofano»: le variabili del metodo vengono di fatto copiate all’interno della classe locale, non mantenute per riferimento. Se una variabile potesse cambiare, la classe locale potrebbe lavorare con una copia obsoleta — ed è una strada verso bug e mal di testa.

Esempio di accesso alle variabili

public class Outer {
    private String secret = "segreto!";

    void revealSecret() {
        String greeting = "Ciao,"; // effectively final

        class Revealer {
            void printSecret() {
                System.out.println(greeting + " " + secret);
            }
        }

        Revealer revealer = new Revealer();
        revealer.printSecret();
    }
}

3. Uso delle classi locali: perché e quando?

Le classi locali non sono uno strumento da usare ogni giorno, ma talvolta rendono il codice più pulito e compatto. Ecco quando conviene usarle:

  • Logica di supporto, specifica di un singolo metodo.
    Ad esempio, se in un metodo occorre implementare un ordinamento complesso, un filtraggio o una struttura dati temporanea.
  • Incapsulamento, per non «sporcare» la classe con tipi superflui.
    Se una classe serve in un solo punto, perché renderla visibile all’intera classe o al package?
  • Implementazione di pattern progettuali in cui oggetti di supporto servono solo localmente.

Esempio: ordinamento con una classe locale

Supponiamo di avere un elenco di nomi e di volerli ordinare per lunghezza. Si può creare una classe locale comparatore:

import java.util.*;

public class NameSorter {
    public void sortNames(List<String> names) {
        class LengthComparator implements Comparator<String> {
            @Override
            public int compare(String a, String b) {
                return Integer.compare(a.length(), b.length());
            }
        }
        Collections.sort(names, new LengthComparator());
    }
}

Esempio: struttura temporanea

A volte è necessario creare una struttura di supporto per elaborare i dati all’interno di un unico metodo:

void processScores(int[] scores) {
    class ScoreInfo {
        int score;
        boolean passed;

        ScoreInfo(int score) {
            this.score = score;
            this.passed = score >= 60;
        }
    }

    for (int s : scores) {
        ScoreInfo info = new ScoreInfo(s);
        System.out.println("Punteggio: " + info.score + ", superato: " + info.passed);
    }
}

4. Classi locali vs classi anonime

A volte nasce la domanda: a cosa servono le classi locali, se ci sono quelle anonime? Vediamolo.

  • Classe locale — è una classe con nome, che puoi riutilizzare più volte nel metodo; puoi aggiungervi campi, più metodi e perfino classi annidate.
  • Classe anonima — è un’implementazione usa e getta di un’interfaccia o di una classe base, senza nome, di solito con un solo metodo (o con l’override di uno‑due metodi).

Quando usarle:

  • Se la logica è semplice e serve una sola volta — usa una classe anonima.
  • Se servono più metodi o campi, o se la classe verrà usata in più punti del metodo — usa una classe locale.

Esempio di confronto

Classe anonima:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Veloce e anonimo!");
    }
};

Classe locale:

void doWork() {
    class MyWorker implements Runnable {
        @Override
        public void run() {
            System.out.println("Sono una classe locale!");
        }
    }
    MyWorker worker = new MyWorker();
    worker.run();
}

5. Limitazioni e particolarità delle classi locali

  • Modificatori di accesso: Una classe locale non può essere dichiarata come public, private o protected. Non è possibile aggiungere neanche static.
  • Membri statici: Una classe locale non può contenere membri statici, tranne le costanti (static final).
  • Ambito di visibilità: Una classe locale è visibile solo nel blocco in cui è dichiarata.
  • Uso di parametri generics: Una classe locale può essere generica, se necessario.
  • Annidamento: È possibile dichiarare classi locali all’interno di altre classi locali (ma meglio non esagerare, altrimenti il codice sembrerà una matrioska).

6. Classi locali in un’applicazione reale

Continuiamo a far evolvere la nostra app didattica. Supponiamo di avere una classe Quiz che pone domande all’utente e verifica le risposte. Vogliamo, all’interno del metodo di verifica, creare una classe temporanea che memorizzi la risposta dell’utente e lo stato della verifica.

Esempio: uso di una classe locale nell’applicazione

import java.util.Scanner;

public class Quiz {
    private String question = "Capitale della Francia?";
    private String correctAnswer = "Parigi";

    public void runQuiz() {
        Scanner scanner = new Scanner(System.in);

        System.out.println(question);
        String userAnswer = scanner.nextLine();

        // Classe locale per memorizzare il risultato
        class AnswerResult {
            String answer;
            boolean isCorrect;

            AnswerResult(String answer) {
                this.answer = answer;
                this.isCorrect = answer.equalsIgnoreCase(correctAnswer);
            }

            void printResult() {
                if (isCorrect) {
                    System.out.println("Corretto!");
                } else {
                    System.out.println("Errato, risposta corretta: " + correctAnswer);
                }
            }
        }

        AnswerResult result = new AnswerResult(userAnswer);
        result.printResult();
    }
}

Qui la classe AnswerResult esiste solo all’interno del metodo runQuiz e non serve altrove. È un ottimo esempio di classe locale nella pratica!

7. Errori comuni con le classi locali

Errore n. 1: tentativo di accedere a una variabile del metodo che non è final o effectively final.
Se dichiari una variabile nel metodo e poi ne modifichi il valore dopo averla usata nella classe locale, il compilatore genererà subito un errore. Assicurati sempre che tali variabili non cambino dopo l’inizializzazione.

Errore n. 2: voler aggiungere modificatori di accesso o static.
Una classe locale non può essere dichiarata con i modificatori public, private, protected o static. Se ci provi, il compilatore protesterà.

Errore n. 3: tentare di usare una classe locale fuori dal metodo.
Una classe locale vive solo nel metodo (o blocco) in cui è dichiarata. Da altri metodi o dall’esterno della classe non è accessibile.

Errore n. 4: abusare delle classi locali per logiche complesse.
Se la tua classe locale diventa troppo grande, contiene molti metodi o campi, probabilmente è il caso di estrarla in una classe separata. Le classi locali sono adatte a compiti compatti e di supporto.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION