W3docs

Metodi Default e Static nelle Interfacce Java

Aggiungi metodi default e static alle interfacce Java per far evolvere le API senza rompere le implementazioni esistenti.

Fino a Java 7, un'interfaccia poteva solo dichiarare metodi astratti — i corpi risiedevano nelle classi che la implementavano. Java 8 ha aggiunto tre nuove cose che puoi inserire all'interno di un'interfaccia:

  • metodi default — un metodo con un corpo, ereditato dagli implementatori che non lo sovrascrivono.
  • metodi static — metodi di utilità di proprietà dell'interfaccia stessa.
  • metodi private (Java 9) — helper condivisi tra i metodi default e static dell'interfaccia.

Il caso d'uso motivante era l'evoluzione delle API. Una volta che java.util.Collection era in circolazione da quindici anni con milioni di implementazioni, il team Java voleva aggiungere stream() senza romperle tutte. default Stream<E> stream() { ... } ha fatto esattamente questo.

Metodi default

Un metodo default ha un corpo. Le classi che implementano l'interfaccia lo ereditano gratuitamente e possono sovrascriverlo se vogliono un comportamento diverso:

public interface Greeter {
  String name();

  default String greet() {
    return "Hello, " + name() + "!";
  }
}

public class English implements Greeter {
  public String name() { return "Alice"; }
}

public class Loud implements Greeter {
  public String name()  { return "Bob"; }
  public String greet() { return "HEY " + name().toUpperCase() + "!"; }   // override
}

new English().greet();   // Hello, Alice!
new Loud().greet();      // HEY BOB!

default è solo un marcatore — la parola chiave dice al compilatore "questo è il corpo di un metodo all'interno di un'interfaccia." Senza di essa, un metodo di interfaccia non ha corpo.

Come ogni membro di interfaccia non private, un metodo default è implicitamente public; non puoi renderlo protected o package-private. Non c'è la parola chiave public negli esempi sopra perché è l'unica opzione consentita dal compilatore.

Aggiungere un metodo default a un'interfaccia esistente è una modifica non distruttiva. Gli implementatori che non lo sovrascrivono ereditano il valore predefinito. È questa proprietà che rende possibile l'evoluzione delle interfacce.

Metodi static sulle interfacce

Un metodo static su un'interfaccia appartiene all'interfaccia, non alle istanze. Chiamalo tramite il nome dell'interfaccia:

public interface Path {
  static Path of(String s) { return new SimplePath(s); }
  String value();
}

Path p = Path.of("/tmp/foo");

Un uso comune sono i factory method che producono istanze dell'interfaccia — Path.of, List.of, Map.of, Stream.of. Permettono ai chiamanti di dipendere dall'interfaccia anche durante la costruzione, invece di dover scegliere una classe di implementazione specifica.

I metodi static delle interfacce non vengono ereditati. Li chiami sempre tramite il nome dell'interfaccia, mai tramite una sottoclasse.

Metodi private (Java 9+)

Un metodo private su un'interfaccia è un helper visibile solo agli altri metodi della stessa interfaccia. Permette a due metodi default di condividere logica senza esporla agli implementatori:

public interface Logger {
  default void info(String msg) { log("INFO", msg); }
  default void warn(String msg) { log("WARN", msg); }

  private void log(String level, String msg) {
    System.out.println("[" + level + "] " + msg);
  }
}

Senza private, dovresti copiare il corpo in entrambi i metodi default oppure esporre log come metodo default — il che permetterebbe agli implementatori di sovrascriverlo, cosa che probabilmente non vuoi.

Cosa non possono fare i metodi default

I metodi default vivono sull'interfaccia, che non ha stato. Possono chiamare metodi astratti e altri metodi default/static, ma non possono leggere o scrivere campi di istanza — non ce ne sono da leggere.

public interface Counter {
  int get();
  void increment();

  default void incrementTwice() {     // ok — only calls abstract methods
    increment();
    increment();
  }
}

Questo limita quanto comportamento reale un metodo default può portare. Sono più adatti come sottili livelli di comodità sopra i metodi astratti, non per sostituire completamente l'implementazione.

Conflitti diamond

Quando una classe implementa due interfacce che forniscono entrambe un metodo default con la stessa firma, la classe deve risolvere esplicitamente il conflitto:

interface A { default String hello() { return "A"; } }
interface B { default String hello() { return "B"; } }

class C implements A, B {
  @Override
  public String hello() {
    return A.super.hello() + B.super.hello();   // explicit pick
  }
}

A.super.hello() invoca il default di A; B.super.hello() invoca quello di B. Il compilatore rifiuta di compilare class C implements A, B { } senza un override — non c'è un vincitore automatico. Questa è la risposta di Java al "problema diamond" dell'ereditarietà multipla — quando si presenta, la classe deve fare la scelta.

Anche una sottoclasse vince su un default ereditato: se una superclasse concreta fornisce hello(), questa vince su qualsiasi metodo default proveniente da un'interfaccia.

Quando aggiungere un metodo default

default è lo strumento giusto quando:

  • Vuoi estendere un'interfaccia esistente e ampiamente implementata senza rompere gli implementatori.
  • Il nuovo metodo è genuinamente derivabile dai metodi astratti esistenti.
  • Il default è "ovviamente corretto" — la maggior parte degli implementatori ne sarà soddisfatta.

È lo strumento sbagliato quando:

  • Sei tentato di inserire un comportamento reale basato sullo stato condiviso (usa invece una classe astratta).
  • Il default non è effettivamente utile e tutti gli implementatori lo sovrascriveranno comunque (lascialo semplicemente astratto).

Un esempio pratico

java— editable, runs on the server

Cosa c'è dopo

Le interfacce e le classi astratte riguardano come i tipi si relazionano tra file diversi. Il prossimo argomento — le classi annidate — riguarda come i tipi si relazionano all'interno di un singolo file: classi dichiarate dentro altre classi e le quattro varianti che Java offre. Continua con classi annidate.

Esercitazione

Pratica
Qual è il principale vantaggio pratico di contrassegnare un nuovo metodo di interfaccia come default invece di lasciarlo astratto?
Qual è il principale vantaggio pratico di contrassegnare un nuovo metodo di interfaccia come default invece di lasciarlo astratto?
Was this page helpful?