1. Abilități

Pentru a înțelege mai bine beneficiile interfețelor și unde să le folosim, trebuie să vorbim despre câteva lucruri mai abstracte.

O clasă modelează de obicei un anumit obiect. O interfață corespunde mai puțin obiectelor și mai mult abilităților sau rolurilor acestora.

Esența interfețelor

De exemplu, lucruri precum mașinile, bicicletele, motocicletele și roțile sunt cel mai bine reprezentate ca clase și obiecte. Dar abilitățile lor – precum „Pot fi călărit”, „Pot transporta oameni”, „Pot suporta” – sunt mai bine prezentate ca interfețe. Aici sunt cateva exemple:

Cod Descriere
interface CanMove
{
   void move(String newLocation);
}
Corespunde capacitatii de miscare
interface Rideable
{
   void ride(Passenger passenger);
}
Corespunde capacității de a fi călărit
interface CanTransport
{
   void addStuff(Object stuff);
   Object removeStuff();
}
Corespunde capacității de a transporta lucruri
class Wheel implements CanMove
{
   ...
}
Clasa Wheelse poate muta
class Car implements CanMove, Rideable, CanTransport
{
   ...
}
Clasa Carse poate muta, poate fi călărit și poate transporta lucruri
class Skateboard implements CanMove, Rideable
{
   ...
}
Clasa Skateboardse poate mișca și poate fi călărit


2. Roluri

Interfețele simplifică foarte mult viața unui programator. Foarte des, un program are mii de obiecte, sute de clase, dar doar câteva zeci de interfețe , adică roluri . Sunt puține roluri, dar există multe modalități de a le combina (clasele).

Ideea este că nu trebuie să scrieți cod pentru fiecare clasă pentru a interacționa cu orice altă clasă. Trebuie doar să interacționați cu rolurile lor (interfețele).

Imaginează-ți că ești un antrenor de animale de companie. Fiecare dintre animalele de companie cu care lucrați poate avea mai multe abilități diferite. Intri într-o ceartă amicală cu vecinul tău despre ale căror animale de companie pot face cel mai mult zgomot. Pentru a rezolva problema, trebuie doar să aliniezi toate animalele de companie care pot „vorbi” și le dai comanda: Vorbește!

Nu-ți pasă ce fel de animal sunt sau ce alte abilități au. Chiar dacă pot face un triplu salt înapoi. În acest moment anume, ești interesat doar de capacitatea lor de a vorbi tare. Iată cum ar arăta în cod:

Cod Descriere
interface CanSpeak
{
   void speak();
}
Abilitatea CanSpeak. Această interfață înțelege comanda către speak, ceea ce înseamnă că are o metodă corespunzătoare.
class Cat implements CanSpeak
{
   void speak()
   {
      println("MEOW");
   }
}

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

class Fish
{
   ...
}
Animale care au această caracteristică.

Pentru a facilita înțelegerea, am furnizat numele claselor în engleză. Acest lucru este permis în Java, dar este extrem de nedorit.













Nostru Fishnu are capacitatea de a vorbi (nu implementează interfața CanSpeak).

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();
      }
   }
}
Și cum le dăm comanda?

Când numărul de clase din programele tale ajunge la mii, nu vei putea trăi fără interfețe. În loc să descriem interacțiunea a mii de clase, este suficient să descriem interacțiunea a câteva zeci de interfețe - acest lucru simplifică foarte mult viața.

Și atunci când este combinată cu polimorfismul, această abordare este în general un succes uluitor.



3. defaultImplementarea metodelor de interfață

Clasele abstracte pot avea variabile și implementări de metode, dar nu pot avea moștenire multiplă. Interfețele nu pot avea variabile sau implementări de metode, dar care pot avea moștenire multiplă.

Situația este exprimată în următorul tabel:

Abilitatea/proprietatea Clasele abstracte Interfețe
Variabile
Implementarea metodei
Moștenirea multiplă

Așadar, unii programatori și-au dorit foarte mult ca interfețele să aibă capacitatea de a avea implementări de metode. Dar a avea capacitatea de a adăuga o implementare a unei metode nu înseamnă că va fi întotdeauna adăugată una. Adăugați-l dacă doriți. Sau dacă nu, atunci nu.

În plus, problemele cu moștenirea multiplă se datorează în primul rând variabilelor. În orice caz, asta au decis și au făcut. Începând cu JDK 8, Java a introdus capacitatea de a adăuga implementări de metode la interfețe.

Iată un tabel actualizat (pentru JDK 8 și mai sus):

Abilitatea/proprietatea Clasele abstracte Interfețe
Variabile
Implementarea metodei
Moștenirea multiplă

Acum, pentru clasele abstracte, precum și pentru interfețe, puteți declara metode cu sau fără implementare. Și aceasta este o veste excelentă!

În clasele abstracte, metodele fără implementare trebuie să fie precedate de abstractcuvântul cheie. Nu trebuie să adăugați nimic înaintea metodelor cu o implementare. În interfețe, este adevărat opusul. Dacă o metodă nu are o implementare, atunci nu trebuie adăugat nimic. Dar dacă există o implementare, atunci defaulttrebuie adăugat cuvântul cheie.

Pentru simplitate, prezentăm aceste informații în următorul tabel mic:

Abilitatea/proprietatea Clasele abstracte Interfețe
Metode fără implementare abstract
Metode cu o implementare default

Problemă

Utilizarea interfețelor care au metode poate simplifica foarte mult ierarhiile mari de clase. De exemplu, abstractul InputStreamși OutputStreamclasele pot fi declarate ca interfețe! Acest lucru ne permite să le folosim mult mai des și mult mai convenabil.

Dar există deja zeci de milioane (miliarde?) de clase Java în lume. Și dacă începi să schimbi bibliotecile standard, atunci s-ar putea să spargi ceva. Ca totul! 😛

Pentru a nu rupe accidental programele și bibliotecile existente, s-a decis ca implementările metodelor în interfețe să aibă cea mai mică precedență de moștenire .

De exemplu, dacă o interfață moștenește o altă interfață care are o metodă, iar prima interfață declară aceeași metodă, dar fără o implementare, atunci implementarea metodei din interfața moștenită nu va ajunge la interfața moștenitoare. Exemplu:

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
{
}

Codul nu se va compila deoarece Tomclasa nu implementează meow()metoda.