1. Capacità

Per comprendere meglio i vantaggi delle interfacce e dove utilizzarle, dobbiamo parlare di alcune cose più astratte.

Una classe di solito modella un particolare oggetto. Un'interfaccia corrisponde meno agli oggetti e più alle loro abilità o ruoli.

L'essenza delle interfacce

Ad esempio, cose come automobili, biciclette, motociclette e ruote sono meglio rappresentate come classi e oggetti. Ma le loro capacità - come "posso essere cavalcato", "posso trasportare persone", "posso stare in piedi" - sono meglio presentate come interfacce. Ecco alcuni esempi:

Codice Descrizione
interface CanMove
{
   void move(String newLocation);
}
Corrisponde alla capacità di muoversi
interface Rideable
{
   void ride(Passenger passenger);
}
Corrisponde alla capacità di essere cavalcato
interface CanTransport
{
   void addStuff(Object stuff);
   Object removeStuff();
}
Corrisponde alla capacità di trasportare cose
class Wheel implements CanMove
{
   ...
}
La Wheelclasse può muoversi
class Car implements CanMove, Rideable, CanTransport
{
   ...
}
La Carclasse può muoversi, essere cavalcata e trasportare cose
class Skateboard implements CanMove, Rideable
{
   ...
}
La Skateboardclasse può muoversi ed essere cavalcata


2. Ruoli

Le interfacce semplificano notevolmente la vita di un programmatore. Molto spesso, un programma ha migliaia di oggetti, centinaia di classi, ma solo un paio di dozzine di interfacce , ad esempio ruoli . Ci sono pochi ruoli, ma ci sono molti modi per combinarli (classi).

Il punto è che non devi scrivere codice in ogni classe per interagire con ogni altra classe. Devi solo interagire con i loro ruoli (interfacce).

Immagina di essere un addestratore di animali domestici. Ognuno degli animali domestici con cui lavori può avere diverse abilità. Entri in una discussione amichevole con il tuo vicino riguardo a quali animali domestici possono fare più rumore. Per sistemare la questione, basta mettere in fila tutti gli animali che possono "parlare", e dare loro il comando: Parla!

Non ti importa che tipo di animale siano o quali altre abilità abbiano. Anche se possono fare una tripla capriola all'indietro. In questo particolare momento, sei interessato solo alla loro capacità di parlare ad alta voce. Ecco come sarebbe nel codice:

Codice Descrizione
interface CanSpeak
{
   void speak();
}
L' CanSpeakabilità. Questa interfaccia comprende il comando to speak, il che significa che ha un metodo corrispondente.
class Cat implements CanSpeak
{
   void speak()
   {
      println("MEOW");
   }
}

class Dog implements CanSpeak
{
   void speak()
   {
      println("WOOF");
   }
}

class Fish
{
   ...
}
Animali che hanno questa caratteristica.

Per facilitare la comprensione, abbiamo fornito i nomi delle classi in inglese. Questo è consentito in Java, ma è altamente indesiderabile.













Il nostro Fishnon ha la capacità di parlare (non implementa l' CanSpeakinterfaccia).

public static void main(String[] args)
{
   // Add all the animals to the list
   ArrayList pets = new ArrayList();
   pets.add(new Cat());
   pets.add(new Dog());
   pets.add(new Fish());

   // If the ability exists, then make a sound
   for(Object pet: pets)
   {
      if (pet instanceof CanSpeak)
      {
         CanSpeak loudmouth = (CanSpeak) pet;
         loudmouth.speak();
      }
   }
}
E come diamo loro il comando?

Quando il numero di classi nei tuoi programmi raggiunge le migliaia, non sarai in grado di vivere senza interfacce. Invece di descrivere l'interazione di migliaia di classi, è sufficiente descrivere l'interazione di poche dozzine di interfacce: questo semplifica enormemente la vita.

E se combinato con il polimorfismo, questo approccio è generalmente un successo strepitoso.



3. L' defaultimplementazione di metodi di interfaccia

Le classi astratte possono avere variabili e implementazioni di metodi, ma non possono avere ereditarietà multipla. Le interfacce non possono avere variabili o implementazioni di metodi, ma possono avere ereditarietà multipla.

La situazione è espressa nella seguente tabella:

Capacità/proprietà Classi astratte Interfacce
Variabili
Implementazione del metodo
Ereditarietà multipla

Quindi, alcuni programmatori volevano davvero che le interfacce avessero la possibilità di avere implementazioni di metodi. Ma avere la possibilità di aggiungere un'implementazione del metodo non significa che ne verrà sempre aggiunta una. Aggiungilo se vuoi. O se non lo fai, allora non farlo.

Inoltre, i problemi con l'ereditarietà multipla sono principalmente dovuti alle variabili. In ogni caso, è quello che hanno deciso e fatto. A partire da JDK 8, Java ha introdotto la possibilità di aggiungere implementazioni di metodi alle interfacce.

Ecco una tabella aggiornata (per JDK 8 e versioni successive):

Capacità/proprietà Classi astratte Interfacce
Variabili
Implementazione del metodo
Ereditarietà multipla

Ora per classi astratte e interfacce, puoi dichiarare metodi con o senza un'implementazione. E questa è un'ottima notizia!

Nelle classi astratte, i metodi senza implementazione devono essere preceduti dalla abstractparola chiave. Non è necessario aggiungere nulla prima dei metodi con un'implementazione. Nelle interfacce, è vero il contrario. Se un metodo non ha un'implementazione, non dovrebbe essere aggiunto nulla. Ma se esiste un'implementazione, è defaultnecessario aggiungere la parola chiave.

Per semplicità presentiamo queste informazioni nella seguente piccola tabella:

Capacità/proprietà Classi astratte Interfacce
Metodi senza implementazione abstract
Metodi con un'implementazione default

Problema

L'uso di interfacce che dispongono di metodi può semplificare notevolmente le gerarchie di classi di grandi dimensioni. Ad esempio, l'abstract InputStreame OutputStreamle classi possono essere dichiarate come interfacce! Questo ci consente di usarli molto più spesso e molto più comodamente.

Ma ci sono già decine di milioni (miliardi?) di classi Java nel mondo. E se inizi a cambiare le librerie standard, potresti rompere qualcosa. Come tutto! 😛

Per non interrompere accidentalmente i programmi e le librerie esistenti, è stato deciso che le implementazioni dei metodi nelle interfacce avrebbero avuto la precedenza di ereditarietà più bassa .

Ad esempio, se un'interfaccia eredita un'altra interfaccia che ha un metodo e la prima interfaccia dichiara lo stesso metodo ma senza un'implementazione, l'implementazione del metodo dall'interfaccia ereditata non raggiungerà l'interfaccia che eredita. Esempio:

interface Pet
{
   default void meow()
   {
      System.out.println("Meow");
   }
}

interface Cat extends Pet
{
   void meow(); // Here we override the default implementation by omitting an implementation
}

class Tom implements Cat
{
}

Il codice non verrà compilato perché la Tomclasse non implementa il meow()metodo.