W3docs

Classi Anonime Interne in Java

Crea implementazioni monouso di interfacce e classi astratte in Java con le classi anonime interne.

Una classe anonima è una sottoclasse o implementazione di interfaccia monouso che si definisce e istanzia in un'unica espressione — senza nome, senza file separato, senza intestazione di classe. Era il modo in cui Java gestiva callback, listener e piccoli adattatori prima che le espressioni lambda arrivassero in Java 8. I lambda hanno sostituito la maggior parte dei loro casi d'uso, ma le classi anonime sono ancora valide, ancora utili in alcune situazioni specifiche, e compaiono ancora nelle codebase più vecchie.

La sintassi

La forma è new SomeType() { ... body ... }. Il corpo è la definizione della classe; l'espressione circostante crea anche un'istanza:

Runnable r = new Runnable() {
  @Override
  public void run() {
    System.out.println("hi");
  }
};
r.run();

Questa singola istruzione (1) definisce una nuova classe che implementa Runnable, (2) crea un'istanza di essa, (3) assegna l'istanza a r. La classe non ha nome nel codice sorgente; il compilatore le assegna un nome generato come Outer$1 nel file .class.

Si può fare lo stesso con una classe astratta:

Shape s = new Shape() {
  @Override double area() { return 42; }
};

…o anche con una classe concreta, se si vuole sovrascrivere uno dei suoi metodi inline:

ArrayList<String> chatty = new ArrayList<>() {
  @Override
  public boolean add(String s) {
    System.out.println("added " + s);
    return super.add(s);
  }
};

Quando usarle

Il caso classico: un callback la cui implementazione è minima e viene utilizzata in un solo posto:

button.addClickListener(new ClickListener() {
  @Override
  public void onClick() {
    System.out.println("clicked");
  }
});

In Java moderno, di solito si scriverebbe come lambda:

button.addClickListener(() -> System.out.println("clicked"));

…e il lambda è più breve, più facile da leggere, e non mantiene un riferimento alla classe esterna. Quindi quando ha ancora senso la forma anonima?

  • Metodi multipli. Un lambda implementa un singolo metodo astratto. Se occorre sovrascrivere due metodi su una classe astratta o implementarne due su un'interfaccia, solo una classe anonima è adatta.
  • Sottoclassare una classe concreta. I lambda supportano solo interfacce funzionali. Per sovrascrivere un metodo su ArrayList, HashMap, o la propria classe concreta al volo, è necessaria una classe anonima.
  • Chiamare super.method(...) nella sovrascrittura. I lambda non hanno super. Le classi anonime sì.
  • Blocchi di inizializzazione. Le classi anonime possono avere blocchi di inizializzazione di istanza ({ ... }); i lambda no.

In pratica si tratta di un insieme ristretto di casi. La maggior parte dei callback moderni usa i lambda.

Cattura di variabili

Una classe anonima dichiarata all'interno di un metodo può leggere le variabili locali del metodo che la contiene — ma solo se sono final o effettivamente final (mai riassegnate dopo l'inizializzazione):

void schedule() {
  String msg = "hello";
  Runnable r = new Runnable() {
    @Override public void run() {
      System.out.println(msg);     // captures msg
    }
  };
  r.run();
  // msg = "bye";          // would make msg no longer effectively final → ERROR above
}

La stessa regola si applica ai lambda — è una proprietà del codice circostante, non della forma sintattica. Il motivo è che il valore catturato viene copiato nei campi della classe sintetica; se msg potesse cambiare in seguito, la copia catturata diventerebbe silenziosamente obsoleta.

Hanno un this esterno

Come le classi interne, le classi anonime dichiarate in un contesto non statico portano un riferimento all'istanza che le contiene. Ciò significa che this all'interno della classe anonima si riferisce all'istanza anonima, non a quella esterna. Per raggiungere l'istanza esterna, qualificarla con Outer.this:

public class Server {
  String name = "outer";

  Runnable handler() {
    return new Runnable() {
      String name = "inner";
      public void run() {
        System.out.println(name);            // "inner"
        System.out.println(Server.this.name);// "outer"
      }
    };
  }
}

E la stessa avvertenza sul riferimento esterno delle classi interne vale anche qui: restituire un'istanza anonima a codice di lunga durata mantiene viva l'istanza esterna.

Limitazioni

  • Una classe anonima può estendere una superclasse oppure implementare una interfaccia — non entrambe, e non più di una per tipo.
  • Non può avere un costruttore proprio — non c'è un nome da assegnare al costruttore. È possibile usare un blocco di inizializzazione di istanza ({ ... }) per la configurazione.
  • Non può essere static.

Confronto con i lambda — il confronto in codice

Stesso compito, due modi:

// Anonymous class — older style
Comparator<String> byLength = new Comparator<String>() {
  @Override
  public int compare(String a, String b) {
    return Integer.compare(a.length(), b.length());
  }
};

// Lambda — modern equivalent
Comparator<String> byLength = (a, b) -> Integer.compare(a.length(), b.length());

Entrambi producono un Comparator<String>. Il lambda è più breve e ha una semantica di this/scope leggermente diversa (nessun riferimento alla classe esterna per i contesti di istanza; this all'interno di un lambda è l'istanza che lo contiene, non un nuovo oggetto). Se entrambe le forme funzionano per il proprio caso d'uso, preferire il lambda.

Un esempio pratico

java— editable, runs on the server

Cosa c'è dopo

Il tipo rimanente di classe annidata è la classe locale — una classe dichiarata all'interno del corpo di un metodo, con un nome reale ma un ambito molto ristretto. Si sovrappone alle classi anonime (e ai lambda), ma a volte è la scelta più pulita. Continua con le classi locali.

Esercizio

Pratica
In Java moderno, quale compito richiede ancora una classe anonima invece di un lambda?
In Java moderno, quale compito richiede ancora una classe anonima invece di un lambda?
Was this page helpful?