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.
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 |
---|---|
|
Corrisponde alla capacità di muoversi |
|
Corrisponde alla capacità di essere cavalcato |
|
Corrisponde alla capacità di trasportare cose |
|
La Wheel classe può muoversi |
|
La Car classe può muoversi, essere cavalcata e trasportare cose |
|
La Skateboard classe 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 |
---|---|
|
L' CanSpeak abilità. Questa interfaccia comprende il comando to speak , il che significa che ha un metodo corrispondente. |
|
Animali che hanno questa caratteristica.
Per facilitare la comprensione, abbiamo fornito i nomi delle classi in inglese. Questo è consentito in Java, ma è altamente indesiderabile.
|
|
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' default
implementazione 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 abstract
parola 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, è default
necessario 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 InputStream
e OutputStream
le 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 Tom
classe non implementa il meow()
metodo.
GO TO FULL VERSION