W3docs

Interfacce Java

Definisci contratti in Java con le interfacce: metodi astratti, metodi default ed ereditarietà multipla di tipo.

Un'interfaccia è un contratto: un insieme nominato di operazioni che qualsiasi classe implementante si impegna a fornire. Le interfacce non hanno stato di istanza, costruttori, né (con un'eccezione trattata nel capitolo successivo) corpi di metodi. Descrivono cosa può fare un tipo, lasciando ogni come alle implementazioni.

Le interfacce sono la risposta di Java all'ereditarietà multipla. Una classe estende esattamente una classe, ma può implementare un numero qualsiasi di interfacce — permettendo di combinare Comparable, AutoCloseable e Iterable sullo stesso tipo senza ambiguità.

Dichiarare un'interfaccia

Usa interface al posto di class:

public interface Shape {
  double area();              // implicitly public abstract
  double perimeter();
}

Le dichiarazioni di metodo in un'interfaccia sono implicitamente public abstract. Puoi scrivere quei modificatori, ma la maggior parte delle guide di stile li omette come ridondanti.

Implementare un'interfaccia

Una classe la dichiara con implements. La classe deve fornire un corpo per ogni metodo dichiarato dall'interfaccia:

public class Circle implements Shape {
  private final double r;
  public Circle(double r) { this.r = r; }

  @Override public double area()      { return Math.PI * r * r; }
  @Override public double perimeter() { return 2 * Math.PI * r; }
}

Se una classe implementa un'interfaccia ma non fornisce tutti i metodi, la classe deve essere dichiarata abstract — la stessa regola vale per le classi astratte.

Puoi anche implementare molte interfacce contemporaneamente:

public class Money implements Comparable<Money>, java.io.Serializable {
  private final long cents;
  public Money(long cents) { this.cents = cents; }
  public int compareTo(Money other) { return Long.compare(this.cents, other.cents); }
}

Questa è l'"ereditarietà multipla di tipo" — Money è sia un Comparable<Money> che un Serializable. Il codice che ha bisogno di uno dei due può prendere un Money.

Programmare rispetto a un'interfaccia

Lo scopo delle interfacce è scrivere codice rispetto al contratto, non all'implementazione:

public double sumAreas(List<Shape> shapes) {
  double sum = 0;
  for (Shape s : shapes) sum += s.area();
  return sum;
}

sumAreas non sa né si preoccupa di Circle. Aggiungi Square implements Shape, Triangle implements Shape, e la funzione funziona anche con liste di questi — senza modifiche.

La libreria standard è costruita su questo pattern. Si dichiara quasi sempre una variabile di tipo interfaccia e si istanzia un tipo concreto:

List<String>   names = new ArrayList<>();        // List, not ArrayList
Map<String, Integer> counts = new HashMap<>();   // Map,  not HashMap

Se in seguito si passa a LinkedList o LinkedHashMap, cambia solo la riga con new.

Costanti nelle interfacce

Ogni campo dichiarato in un'interfaccia è implicitamente public static final — una costante. Di solito non si aggiungono costanti alle interfacce (è considerato cattivo stile — l'Antipattern dell'Interfaccia Costante), ma la sintassi esiste:

public interface Color {
  String DEFAULT = "black";    // implicitly public static final
}

Se hai bisogno di costanti, preferisci un enum o una semplice final class con campi public static final.

Le interfacce possono estendere interfacce

Un'interfaccia può estendere altre interfacce — anche più di una contemporaneamente:

public interface Readable    { String read(); }
public interface Writable    { void write(String s); }
public interface ReadWrite extends Readable, Writable { }

Ora qualsiasi classe che implementa ReadWrite deve fornire sia read() che write(). Non c'è distinzione tra class extends e interface implements qui — le interfacce estendono interfacce con extends.

Metodi default e statici (anteprima)

Da Java 8, le interfacce possono avere metodi default (corpi forniti tramite la parola chiave default) e metodi static. Permettono di aggiungere comportamento a un'interfaccia senza rompere ogni implementazione esistente. Il trattamento completo è nel prossimo capitolo, metodi default:

public interface Shape {
  double area();

  // Default method — implementors get this for free.
  default String describe() {
    return getClass().getSimpleName() + " area=" + area();
  }

  // Static factory on the interface itself.
  static Shape unitCircle() { return new Circle(1); }
}

Interfacce marker

Un'interfaccia marker non dichiara metodi. Esiste puramente come etichetta che il codice a runtime può verificare:

public interface Cacheable { }      // no methods

public class Snapshot implements Cacheable { ... }

Poi da qualche altra parte: if (obj instanceof Cacheable) { ... }. Il Java moderno preferisce le annotazioni per questo tipo di metadati (@Cacheable invece di implements Cacheable), ma Serializable, Cloneable e RandomAccess sono ben note interfacce marker nella libreria standard.

Interfacce funzionali

Un'interfaccia con esattamente un metodo astratto è un'interfaccia funzionale. È importante perché Java permette di fornire tale interfaccia con un'espressione lambda o un riferimento a metodo invece di una classe anonima completa — la lambda è l'implementazione di quell'unico metodo.

@FunctionalInterface
public interface Transformer {
  String apply(String input);     // the single abstract method
}

Transformer upper = s -> s.toUpperCase();   // lambda implements apply
System.out.println(upper.apply("hi"));      // prints: HI

L'annotazione opzionale @FunctionalInterface è una guardia a tempo di compilazione: il codice non compilerà se l'interfaccia finisce per avere più di un metodo astratto. (I metodi default e statici non contano verso il limite.) La libreria standard include un'intera cassetta degli attrezzi di queste — Runnable, Comparator, Function, Predicate, Supplier — trattate in interfacce funzionali.

Scegliere tra un'interfaccia e una classe astratta

L'albero delle decisioni:

  1. Le sottoclassi devono condividere stato o implementazioni di metodi condivisi? Se sì, probabilmente vuoi una classe astratta — le interfacce non possono contenere stato di istanza.
  2. Vuoi un tipo che diverse classi altrimenti non correlate possano soddisfare? Se sì, un'interfaccia — una classe può implementare molte interfacce ma estendere solo una classe.
  3. Il contratto crescerà nel tempo? Le interfacce evolvono con più attenzione — aggiungere un metodo astratto a un'interfaccia rompe ogni implementazione a meno che non lo si renda un metodo default. Le classi astratte possono aggiungere un metodo concreto senza rompere nessuno.

Nella maggior parte dei codebase reali, le interfacce vincono per i contratti di tipo e le classi astratte appaiono in background quando diverse implementazioni condividono un'infrastruttura comune.

Un esempio pratico

java— editable, runs on the server

Cosa c'è dopo

Le interfacce erano contratti puri — nessun corpo di metodo. Da Java 8 questo è cambiato: le interfacce possono fornire metodi default, metodi static e persino metodi helper private. Il prossimo capitolo è un tour di queste aggiunte. Continua con metodi default.

Esercitazione

Pratica
Qual è il motivo pratico per cui Java non permette a una classe di estendere più di una classe ma consente a una classe di implementare molte interfacce?
Qual è il motivo pratico per cui Java non permette a una classe di estendere più di una classe ma consente a una classe di implementare molte interfacce?
Was this page helpful?