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 dispone di interfacce. In senso lato, un'interfaccia è una sorta di "telecomando" che collega due parti interagenti. Un semplice esempio di interfaccia nella vita di tutti i giorni è il telecomando di una TV. Collega due oggetti - una persona e una TV - e svolge diverse attività: alzare o abbassare il volume, cambiare canale e accendere o spegnere la TV. Una parte (la persona) deve accedere all'interfaccia (premere un pulsante sul telecomando) per fare in modo che la seconda parte esegua l'azione. Ad esempio, per far passare la TV al canale successivo. Inoltre, l'utente non t è necessario sapere come è organizzata la TV o come viene implementato internamente il processo di cambio canale. L'unica cosa a cui l'utente ha accesso è l'interfaccia. L'obiettivo principale è quello di ottenere il risultato desiderato. Cosa c'entra questo con la programmazione e Java? Tutto :) La creazione di un'interfaccia è molto simile alla creazione di una normale classe, ma usa invece la parolaclass , indichiamo la parola interfaccia . Diamo un'occhiata all'interfaccia Java più semplice, vediamo come funziona e perché ne avremmo bisogno:
public interface CanSwim {
public void swim();
}
Abbiamo creato un'interfaccia CanSwim . È un po' come il nostro telecomando, ma con un 'pulsante': il metodo swim() . Ma come si usa questo telecomando? Per fare ciò, dobbiamo implementare un metodo, ovvero il nostro pulsante di controllo remoto. Per utilizzare un'interfaccia, alcune classi nel nostro programma devono implementarne i metodi. Inventiamo una classe i cui oggetti "possono nuotare". Ad esempio, una classe Duck si adatta a:
public class Duck implements CanSwim {
public void swim() {
System.out.println("Duck, swim!");
}
public static void main(String[] args) {
Duck duck = new Duck();
duck.swim();
}
}
"Cosa vediamo qui? La classe Duck è 'associata' all'interfaccia CanSwim tramite la parola chiave implements . Ricorderete che abbiamo usato un meccanismo simile per associare due classi attraverso l'ereditarietà, ma in quel caso abbiamo usato la parola extends. Per Per chiarezza, possiamo tradurre ' public class Duck implements CanSwim ' letteralmente come: 'La public class Duck implementa l' interfaccia CanSwim ". Ciò significa che una classe associata ad un'interfaccia deve implementare tutti i suoi metodi. Nota: la nostra Duck
classe, proprio come l' CanSwim
interfaccia, ha un swim()
metodo e contiene una certa logica.Questo è un requisito obbligatorio.Se ci limitiamo a scriverepublic class Duck implements CanSwim
senza creare un swim()
metodo nella Duck
classe, il compilatore ci darà un errore: Duck non è astratto e non sovrascrive il metodo astratto swim() in CanSwim Perché? Perché succede? Se spieghiamo l'errore utilizzando l'esempio della TV, sarebbe come consegnare a qualcuno un telecomando TV con un pulsante "cambia canale" che non può cambiare canale. Puoi premere il pulsante quanto vuoi, ma non funzionerà. Il telecomando non cambia canale da solo: invia solo un segnale al televisore, che implementa il complesso processo di cambio canale. E così è con la nostra papera: deve saper nuotare per poter essere richiamata tramite l' CanSwim
interfaccia. Se non sa come, ilCanSwim
l'interfaccia non collega le due parti: la persona e il programma. La persona non potrà utilizzare il swim()
metodo per fare una Duck
nuotata all'interno del programma. Ora capisci più chiaramente a cosa servono le interfacce. Un'interfaccia descrive il comportamento che devono avere le classi che implementano l'interfaccia. 'Comportamento' è una raccolta di metodi. Se vogliamo creare diversi messenger, la cosa più semplice da fare è creare un'interfaccia Messenger
. Di cosa ha bisogno ogni messaggero? A livello base, devono essere in grado di ricevere e inviare messaggi.
public interface Messenger{
public void sendMessage();
public void getMessage();
}
Ora possiamo semplicemente creare le nostre classi messenger che implementano l'interfaccia corrispondente. Il compilatore stesso ci "costringerà" a implementarli nelle nostre classi. Telegramma:
public class Telegram implements Messenger {
public void sendMessage() {
System.out.println("Sending a Telegram message!");
}
public void getMessage() {
System.out.println("Receiving a Telegram message!");
}
}
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!");
}
}
Vibro:
public class Viber implements Messenger {
public void sendMessage() {
System.out.println("Sending a Viber message!");
}
public void getMessage() {
System.out.println("Receiving a Viber message!");
}
}
Quali vantaggi offre questo? Il più importante di questi è l'accoppiamento libero. Immagina di progettare un programma che raccolga i dati dei clienti. La Client
classe ha sicuramente bisogno di un campo per indicare quale specifico messenger sta usando il client. Senza interfacce, questo sembrerebbe strano:
public class Client {
private WhatsApp whatsApp;
private Telegram telegram;
private Viber viber;
}
Abbiamo creato tre campi, ma un client può avere un solo messenger. Solo che non sappiamo quale. Quindi dobbiamo aggiungere ogni possibilità alla classe per poter comunicare con il cliente. Si scopre che uno o due di loro saranno sempre null
, del tutto non necessari al programma. È meglio usare invece la nostra interfaccia:
public class Client {
private Messenger messenger;
}
Questo è un esempio di accoppiamento lento! Invece di specificare una classe messenger specifica nella Client
classe, indichiamo semplicemente che il client ha un messenger. Quale esattamente verrà determinato durante l'esecuzione del programma. Ma perché abbiamo bisogno di interfacce per questo? Perché sono stati aggiunti alla lingua? Questa è una buona domanda - e la domanda giusta! Non possiamo ottenere lo stesso risultato usando l'eredità ordinaria? La Messenger
classe come padre e Viber
, Telegram
e WhatsApp
come figli. In effetti, è possibile. Ma c'è un intoppo. Come già sai, Java non ha ereditarietà multipla. Ma c'è il supporto per più interfacce. Una classe può implementare tutte le interfacce che desideri. Immagina di avere una Smartphone
classe che ne ha unoApp
campo, che rappresenta un'app installata sullo smartphone.
public class Smartphone {
private App app;
}
Ovviamente un'app e un messenger sono simili, ma sono comunque cose diverse. Possono esserci versioni mobile e desktop di un messenger, ma l'app rappresenta specificamente un'app mobile. Ecco il punto: se usassimo l'ereditarietà, non saremmo in grado di aggiungere un Telegram
oggetto alla Smartphone
classe. Dopotutto, la Telegram
classe non può ereditare contemporaneamente App
e Messenger
! E l'abbiamo già fatto ereditare Messenger
e aggiunto alla Client
classe. Ma la Telegram
classe può facilmente implementare entrambe le interfacce! Di conseguenza, possiamo dare alla Client
classe un Telegram
oggetto come Messenger
, e possiamo darlo alla Smartphone
classe come App
. Ecco come farlo:
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 stiamo usando la Telegram
classe come vogliamo. In alcuni punti, funge da App
. In altri luoghi, funge da Messenger
. Avrete sicuramente già notato che i metodi di interfaccia sono sempre 'vuoti', cioè non hanno implementazione. Il motivo è semplice: l'interfaccia descrive il comportamento, ma non lo implementa. 'Tutti gli oggetti che implementano l' CanSwim
interfaccia devono essere in grado di nuotare': questo è tutto ciò che ci dice l'interfaccia. Il modo specifico in cui nuotano pesci, anatre e cavalli è una domanda per Fish
, Duck
, eHorse
classi, non l'interfaccia. Proprio come cambiare canale è un compito per la TV. Il telecomando ti dà semplicemente un pulsante per questo. Tuttavia, in Java 8 è apparsa un'aggiunta interessante: i metodi predefiniti. Ad esempio, la tua interfaccia ha 10 metodi. 9 di essi hanno implementazioni diverse in classi diverse, ma uno è implementato uguale per tutti. In precedenza, prima di Java 8, i metodi di interfaccia non avevano alcuna implementazione: il compilatore dava immediatamente un errore. Ora puoi fare qualcosa del genere:
public interface CanSwim {
public default void swim() {
System.out.println("Swim!");
}
public void eat();
public void run();
}
Utilizzando la default
parola chiave, abbiamo creato un metodo di interfaccia con un'implementazione predefinita. Dobbiamo fornire la nostra implementazione per altri due metodi - eat()
e run()
- in tutte le classi che implementano CanSwim
. Non abbiamo bisogno di farlo con il swim()
metodo: l'implementazione sarà la stessa in ogni classe. A proposito, ti sei già imbattuto in interfacce in compiti passati, anche se non l'hai notato :) Ecco un vivido esempio: hai lavorato con le interfacce List
e ! Set
Più precisamente, hai lavorato con le loro implementazioni — ArrayList
, LinkedList
, HashSet
, ecc. Lo stesso diagramma fornisce chiaramente un esempio in cui una classe implementa più interfacce contemporaneamente. Ad esempio, LinkedList
implementa l' List
andDeque
(coda a doppia estremità). Hai familiarità con l' Map
interfaccia, o meglio, con la sua HashMap
implementazione. A proposito, questo diagramma illustra una caratteristica: le interfacce possono ereditare altre interfacce. L' SortedMap
interfaccia eredita Map
, mentre Deque
eredita Queue
. Questo è necessario se vuoi mostrare la relazione tra le interfacce, dove un'interfaccia è una versione estesa di un'altra. Consideriamo un esempio con l' Queue
interfaccia. Non abbiamo ancora recensitoQueues
, ma è piuttosto semplice e funziona come una normale coda, o fila, in un negozio. Puoi solo aggiungere elementi alla fine della coda e puoi prenderli solo dall'inizio. A un certo punto, gli sviluppatori avevano bisogno di una versione migliorata della coda per aggiungere e prendere elementi a entrambe le estremità. Quindi hanno creato un'interfaccia Deque
, che è una coda a doppia estremità. Ha tutti i metodi di una normale coda. Dopotutto, è il genitore della coda a doppia estremità, ma aggiunge anche nuovi metodi.
GO TO FULL VERSION