1. Introduzione alle interfacce

Oggi è il tuo giorno per la conoscenza. Un altro argomento nuovo e interessante sono le interfacce.

Il concetto di interfaccia è figlio dei principi di astrazione e polimorfismo. Un'interfaccia è molto simile a una classe astratta, in cui tutti i metodi sono astratti. È dichiarato allo stesso modo di una classe, ma usiamo la interfaceparola chiave.

interface Feline
{
   void purr();
   void meow();
   void growl();
}

Ecco alcuni fatti utili sulle interfacce:

1. Dichiarazione di un'interfaccia

interface Drawable
{
   void draw();
}

interface HasValue
{
   int getValue();
}
  1. Invece della classparola chiave, scriviamo interface.
  2. Contiene solo metodi astratti (non scrivere la abstractparola chiave)
  3. In effetti, le interfacce hanno tuttipublic i metodi
2. Ereditarietà dell'interfaccia

Un'interfaccia può ereditare solo interfacce. Ma un'interfaccia può avere molti genitori. Un altro modo per dire questo è dire che Java ha un'ereditarietà multipla di interfacce. Esempi:

interface Piece extends Drawable, HasValue
{
   int getX();
   int getY();
}

3. Ereditare le classi dalle interfacce

Una classe può ereditare più interfacce (solo da una classe). Questo viene fatto usando la implementsparola chiave. Esempio:

abstract class ChessItem implements Drawable, HasValue
{
   private int x, y, value;
   public int getValue()
   {
      return value;
   }

   public int getX()
   {
      return x;
   }

   public  int getY()
   {
      return y;
   }
}

La classe ChessItem è dichiarata astratta: implementa tutti i metodi ereditati tranne draw. In altre parole, la ChessItemclasse contiene un metodo astratto — draw().

Il significato tecnico delle parole chiave extendse implementsè lo stesso: entrambi sono ereditarietà. La distinzione è stata fatta per migliorare la leggibilità del codice. Diciamo anche che le classi sono ereditate (tramite extends) e le interfacce sono implementate (tramite implements)

4. Variabili

Ecco la cosa più importante: le variabili ordinarie non possono essere dichiarate nelle interfacce (sebbene quelle statiche possano farlo).

Ma perché abbiamo bisogno di interfacce? Quando vengono utilizzati? Le interfacce hanno due forti vantaggi rispetto alle classi:



2. Separare la "descrizione dei metodi" dalla loro attuazione.

In precedenza, abbiamo detto che se vuoi consentire ai metodi della tua classe di essere chiamati da altre classi, allora i tuoi metodi devono essere contrassegnati con la publicparola chiave. Se vuoi che alcuni di questi metodi vengano chiamati solo dall'interno della tua classe, devi contrassegnarli con la privateparola chiave. In altre parole, dividiamo i metodi della classe in due categorie: "utilizzabili da tutti" e "solo per uso personale".

Le interfacce contribuiscono a rafforzare ulteriormente questa divisione. Creeremo una "classe utilizzabile da tutti" e una seconda classe "solo per uso personale", che erediterà la prima classe. Ecco più o meno come sarebbe:

Prima Dopo
class Student
{
   private String name;
   public Student(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return this.name;
   }

   private void setName(String name)
   {
      this.name = name;
   }
interface Student
{
   public String getName();
}

class StudentImpl implements Student
{
   private String name;
   public StudentImpl(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return this.name;
   }

   private void setName(String name)
   {
      this.name = name;
   }
}
public static void main(String[] args)
{
   Student student = new Student("Alibaba");
   System.out.println(student.getName());
}
public static void main(String[] args)
{
   Student student = new StudentImpl("Ali")
   System.out.println(student.getName());
}

Abbiamo diviso la nostra classe in due: un'interfaccia e una classe che eredita l' interfaccia . E qual è il vantaggio qui?

Molte classi diverse possono implementare (ereditare) la stessa interfaccia. E ognuno può avere il proprio comportamento. Ad esempio, ArrayList LinkedListsono due diverse implementazioni dell'interfaccia List.

Pertanto, nascondiamo non solo le varie implementazioni, ma anche la stessa classe di implementazione (poiché abbiamo bisogno solo dell'interfaccia nel codice). Questo ci permette di essere molto flessibili: proprio come il programma in esecuzione, possiamo sostituire un oggetto con un altro, modificando il comportamento di un oggetto senza influire su tutte le classi che lo utilizzano.

Questa è una tecnica molto potente se combinata con il polimorfismo. Per ora, è tutt'altro che ovvio il motivo per cui dovresti farlo. Devi prima incontrare programmi con dozzine o centinaia di classi per capire che le interfacce possono rendere la tua vita molto più semplice che senza di esse.


3. Ereditarietà multipla

In Java, tutte le classi possono avere solo una classe genitore. In altri linguaggi di programmazione, le classi possono spesso avere più classi padre. Questo è molto conveniente, ma porta anche molti problemi.

I creatori di Java sono arrivati ​​a un compromesso: hanno proibito l'ereditarietà multipla delle classi, ma consentito l'ereditarietà multipla delle interfacce. Un'interfaccia può avere più interfacce padre. Una classe può avere più interfacce genitore ma solo una classe genitore.

Perché hanno vietato l'ereditarietà multipla delle classi ma consentono l'ereditarietà multipla delle interfacce? A causa del cosiddetto problema dell'ereditarietà dei diamanti:

Ereditarietà multipla

Quando la classe B eredita la classe A, non sa nulla delle classi C e D. Quindi usa le variabili della classe A come meglio crede. La classe C fa lo stesso: utilizza le variabili della classe A, ma in modo diverso. E tutto questo si traduce in un conflitto nella classe D.

Diamo un'occhiata al seguente semplice esempio. Diciamo che abbiamo 3 classi:

class Data
{
   protected int value;
}
class XCoordinate extends Data
{
   public void setX (int x) { value = x;}
   public int getX () { return value;}
}
class YCoordinate extends Data
{
   public void setY (int y) { value = y;}
   public int getY () { return value; }
}

La classe Data memorizza la valuevariabile. La sua classe discendente XCoordinate usa quella variabile per memorizzare il xvalore e la YCoordinateclasse discendente la usa per memorizzare il yvalore.

E funziona. Separatamente. Ma se vogliamo che la classe XYCoordinates erediti entrambe le classi XCoordinatee YCoordinate, otteniamo un codice rotto. Questa classe avrà i metodi delle sue classi antenate, ma non funzioneranno correttamente, perché hanno lo stesso file value variable.

Ma poiché le interfacce non possono avere variabili, non possono avere questo tipo di conflitto. Di conseguenza, è consentita l'ereditarietà multipla delle interfacce.