Ciao! Oggi parleremo di un concetto importante in Java: le interfacce. Probabilmente la parola ti è familiare. Ad esempio, la maggior parte dei programmi e dei giochi per computer dispongono di interfacce. In senso lato, un'interfaccia è una sorta di “telecomando” che collega due parti che interagiscono tra loro. Un semplice esempio di interfaccia della vita quotidiana è il telecomando della TV. Collega due oggetti, una persona e una TV, e svolge diverse attività: alzare o abbassare il volume, cambiare canale, accendere o spegnere la TV. Un lato (la persona) deve accedere all'interfaccia (premere il pulsante del telecomando) affinché l'altro lato esegua l'azione. Ad esempio, affinché la TV passi al canale successivo. In questo caso, l'utente non ha bisogno di conoscere il dispositivo del televisore e come viene implementato il processo di cambio canale al suo interno. Tutto ciò a cui l'utente ha accesso è l'interfaccia . Il compito principale è ottenere il risultato desiderato. Cosa c'entra questo con la programmazione e Java? Diretto :) La creazione di un'interfaccia è molto simile alla creazione di una classe normale, ma al posto della parola
class
specifichiamo la parola interface
. Diamo un'occhiata all'interfaccia Java più semplice e scopriamo come funziona e a cosa serve:
public interface Swimmable {
public void swim();
}
Abbiamo creato un'interfaccia Swimmable
che può nuotare . Questo è qualcosa come il nostro telecomando, che ha un “pulsante”: il metodo swim()
è “nuota”. Come possiamo utilizzare questo “ telecomando ”? A questo scopo il metodo, ad es. Il pulsante sul nostro telecomando deve essere implementato. Per utilizzare un'interfaccia, i suoi metodi devono essere implementati da alcune classi del nostro programma. Creiamo una classe i cui oggetti corrispondano alla descrizione "sa nuotare". Ad esempio, la classe duck è adatta Duck
:
public class Duck implements Swimmable {
public void swim() {
System.out.println("Duck, swim!");
}
public static void main(String[] args) {
Duck duck = new Duck();
duck.swim();
}
}
Cosa vediamo qui? Una classe Duck
è associata a un'interfaccia Swimmable
utilizzando la parola chiave implements
. Se ricordi, abbiamo utilizzato un meccanismo simile per collegare due classi in ereditarietà, solo che c'era la parola “ extends ”. “ public class Duck implements Swimmable
” può essere tradotto letteralmente per chiarezza: “una classe pubblica Duck
implementa l’interfaccia Swimmable
”. Ciò significa che una classe associata ad un'interfaccia deve implementare tutti i suoi metodi. Nota: nella nostra classe, Duck
proprio come nell'interfaccia , Swimmable
c'è un metodo swim()
e al suo interno c'è una sorta di logica. Questo è un requisito obbligatorio. Se scrivessimo semplicemente “ public class Duck implements Swimmable
” e non creassimo un metodo swim()
nella classe Duck
, il compilatore ci darebbe un errore: Duck non è astratto e non sovrascrive il metodo astratto swim() in Swimmable Perché succede questo? Se spieghiamo l'errore usando l'esempio di una TV, si scopre che stiamo dando a una persona un telecomando con un pulsante "cambia canale" da una TV che non sa come cambiare canale. A questo punto premi il pulsante quanto vuoi, non funzionerà nulla. Il telecomando in sé non cambia canale: fornisce solo un segnale al televisore, all'interno del quale viene implementato un complesso processo di cambio canale. Così è anche per la nostra papera: deve saper nuotare affinché sia possibile accedervi tramite l'interfaccia Swimmable
. Se non sa come farlo, l'interfaccia Swimmable
non collegherà le due parti: la persona e il programma. Una persona non sarà in grado di utilizzare un metodo swim()
per rendere Duck
mobile un oggetto all'interno di un programma. Ora hai visto più chiaramente a cosa servono le interfacce. Un'interfaccia descrive il comportamento che devono avere le classi che implementano quell'interfaccia. “Comportamento” è un insieme di metodi. Se vogliamo creare più messenger, il modo più semplice per farlo è creare un'interfaccia Messenger
. Cosa dovrebbe essere in grado di fare qualsiasi messenger? In una forma semplificata, ricevi e invia messaggi.
public interface Messenger{
public void sendMessage();
public void getMessage();
}
E ora possiamo semplicemente creare le nostre classi di messaggistica implementando questa interfaccia. Sarà il compilatore stesso a “costringerci” ad implementarli all’interno delle classi. Telegramma:
public class Telegram implements Messenger {
public void sendMessage() {
System.out.println("Sending a message to Telegram!");
}
public void getMessage() {
System.out.println("Reading the message in Telegram!");
}
}
WhatsApp:
public class WhatsApp implements Messenger {
public void sendMessage() {
System.out.println("Sending a WhatsApp message!");
}
public void getMessage() {
System.out.println("Reading a WhatsApp message!");
}
}
Viber:
public class Viber implements Messenger {
public void sendMessage() {
System.out.println("Sending a message to Viber!");
}
public void getMessage() {
System.out.println("Reading a message in Viber!");
}
}
Quali vantaggi offre questo? Il più importante di questi è l'accoppiamento lento. Immagina di progettare un programma in cui raccoglieremo i dati dei clienti. La classe Client
deve avere un campo che indica quale messenger utilizza il client. Senza interfacce sembrerebbe strano:
public class Client {
private WhatsApp whatsApp;
private Telegram telegram;
private Viber viber;
}
Abbiamo creato tre campi, ma un cliente può facilmente avere un solo messenger. Semplicemente non sappiamo quale. E per non rimanere senza comunicazione con il cliente, devi "spingere" tutte le opzioni possibili nella classe. Si scopre che uno o due di essi saranno sempre presenti null
e non saranno affatto necessari affinché il programma funzioni. Invece, è meglio usare la nostra interfaccia:
public class Client {
private Messenger messenger;
}
Questo è un esempio di “accoppiamento allentato”! Invece di specificare una classe messenger specifica nella classe Client
, menzioniamo semplicemente che il client ha un messenger. Quale sarà determinato durante il corso del programma. Ma perché abbiamo bisogno di interfacce per questo? Perché sono stati aggiunti alla lingua? La domanda è buona e corretta! Lo stesso risultato si può ottenere utilizzando l'ereditarietà ordinaria, giusto? La classe Messenger
è la classe genitore e , Viber
e Telegram
sono WhatsApp
gli eredi. In effetti, è possibile farlo. Ma c'è un problema. Come già sai, in Java non esiste l'ereditarietà multipla. Ma esistono molteplici implementazioni di interfacce. Una classe può implementare quante interfacce desidera. Immagina di avere una classe Smartphone
con un campo Application
: un'applicazione installata su uno smartphone.
public class Smartphone {
private Application application;
}
L'applicazione e il messenger sono, ovviamente, simili, ma sono comunque cose diverse. Messenger può essere sia mobile che desktop, mentre Application è un'applicazione mobile. Quindi, se utilizzassimo l'ereditarietà, non saremmo in grado di aggiungere un oggetto Telegram
alla classe Smartphone
. Dopo tutto, una classe Telegram
non può ereditare da Application
e da Messenger
! E siamo già riusciti a ereditarlo da Messenger
e ad aggiungerlo alla classe in questa forma Client
. Ma una classe Telegram
può facilmente implementare entrambe le interfacce! Pertanto, in una classe Client
possiamo implementare un oggetto Telegram
come Messenger
, e in una classe Smartphone
come Application
. Ecco come è fatto:
public class Telegram implements Application, Messenger {
//...methods
}
public class Client {
private Messenger messenger;
public Client() {
this.messenger = new Telegram();
}
}
public class Smartphone {
private Application application;
public Smartphone() {
this.application = new Telegram();
}
}
Ora possiamo usare la classe Telegram
come vogliamo. Da qualche parte agirà nel ruolo di Application
, da qualche parte nel ruolo di Messenger
. Probabilmente hai già notato che i metodi nelle interfacce sono sempre “vuoti”, ovvero non hanno alcuna implementazione. La ragione di ciò è semplice: un'interfaccia descrive il comportamento, non lo implementa. “Tutti gli oggetti delle classi che implementano l’interfaccia Swimmable
devono poter fluttuare”: questo è tutto ciò che ci dice l’interfaccia. Come nuoterà esattamente un pesce, un'anatra o un cavallo è una domanda per le classi Fish
, Duck
e Horse
, e non per l'interfaccia. Proprio come cambiare canale è compito di una TV. Il telecomando ti dà semplicemente un pulsante per farlo. Tuttavia, Java8 ha un'aggiunta interessante: i metodi predefiniti. Ad esempio, la tua interfaccia ha 10 metodi. 9 di essi sono implementati in modo diverso in classi diverse, ma uno è implementato allo stesso modo in tutte. In precedenza, prima del rilascio di Java8, i metodi all'interno delle interfacce non avevano alcuna implementazione: il compilatore generava immediatamente un errore. Ora puoi farlo in questo modo:
public interface Swimmable {
public default void swim() {
System.out.println("Swim!");
}
public void eat();
public void run();
}
Utilizzando la parola chiave default
, abbiamo creato un metodo nell'interfaccia con un'implementazione predefinita. Dovremo implementare gli altri due metodi eat()
e run()
noi stessi in tutte le classi che implementeranno Swimmable
. Non è necessario farlo con il metodo swim()
: l'implementazione sarà la stessa in tutte le classi. A proposito, ti sei imbattuto in interfacce più di una volta nelle attività precedenti, anche se non te ne sei accorto tu stesso :) Ecco un esempio ovvio: hai lavorato con le interfacce List
e Set
! Più precisamente, con le loro implementazioni - , ArrayList
e altri. Lo stesso diagramma mostra un esempio in cui una classe implementa più interfacce contemporaneamente. Ad esempio, implementa le interfacce e (coda fronte-retro). Conosci anche l'interfaccia , o meglio, le sue implementazioni - . A proposito, in questo diagramma puoi vedere una caratteristica: le interfacce possono essere ereditate l'una dall'altra. L'interfaccia è ereditata da ed è ereditata dalla coda . Ciò è necessario se vuoi mostrare la connessione tra le interfacce, ma un'interfaccia è una versione estesa di un'altra. Consideriamo un esempio con un'interfaccia : una coda. Non abbiamo ancora esaminato le collezioni , ma sono piuttosto semplici e disposte come una fila regolare in un negozio. Puoi aggiungere elementi solo alla fine della coda e rimuoverli solo dall'inizio. Ad un certo punto, gli sviluppatori avevano bisogno di una versione ampliata della coda in modo che gli elementi potessero essere aggiunti e ricevuti da entrambe le parti. È così che è stata creata un'interfaccia : una coda bidirezionale. Contiene tutti i metodi di una coda normale, perché è il “genitore” di una coda bidirezionale, ma sono stati aggiunti nuovi metodi. LinkedList
HashSet
LinkedList
List
Deque
Map
HashMap
SortedMap
Map
Deque
Queue
Queue
Queue
Deque
GO TO FULL VERSION