1. Wprowadzenie interfejsów

Dziś jest twój dzień wiedzy. Kolejnym nowym i interesującym tematem są interfejsy.

Koncepcja interfejsu jest dzieckiem zasad abstrakcji i polimorfizmu. Interfejs jest bardzo podobny do klasy abstrakcyjnej, w której wszystkie metody są abstrakcyjne. Deklaruje się ją w taki sam sposób jak klasę, ale używamy słowa interfacekluczowego.

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

Oto kilka przydatnych faktów na temat interfejsów:

1. Deklaracja interfejsu

interface Drawable
{
   void draw();
}

interface HasValue
{
   int getValue();
}
  1. Zamiast classsłowa kluczowego piszemy interface.
  2. Zawiera tylko abstrakcyjne metody (nie pisz słowa abstractkluczowego)
  3. W rzeczywistości interfejsy mają wszystkiepublic metody
2. Dziedziczenie interfejsu

Interfejs może dziedziczyć tylko interfejsy. Ale interfejs może mieć wielu rodziców. Innym sposobem wyrażenia tego jest stwierdzenie, że Java ma wielokrotne dziedziczenie interfejsów. Przykłady:

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

3. Dziedziczenie klas z interfejsów

Klasa może dziedziczyć wiele interfejsów (tylko z jednej klasy). Odbywa się to za pomocą implementssłowa kluczowego. Przykład:

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

Klasa ChessItem jest deklarowana jako abstrakcyjna: implementuje wszystkie odziedziczone metody oprócz draw. Innymi słowy, ChessItemklasa zawiera jedną abstrakcyjną metodę — draw().

Techniczne znaczenie słów kluczowych extendsi implementsjest takie samo: oba są dziedziczeniem. Rozróżnienie to miało na celu poprawę czytelności kodu. Mówimy również, że klasy są dziedziczone (przez extends), a interfejsy są implementowane (przez implements)

4. Zmienne

Oto najważniejsza rzecz: zwykłe zmienne nie mogą być deklarowane w interfejsach (chociaż mogą to być zmienne statyczne).

Ale po co nam interfejsy? Kiedy są używane? Interfejsy mają dwie silne zalety w stosunku do klas:



2. Oddzielenie "opisu metod" od ich implementacji.

Wcześniej powiedzieliśmy, że jeśli chcesz, aby metody twojej klasy były wywoływane z innych klas, to twoje metody muszą być oznaczone słowem publickluczowym. Jeśli chcesz, aby niektóre z tych metod były wywoływane tylko z poziomu Twojej klasy, musisz oznaczyć je słowem privatekluczowym. Innymi słowy, dzielimy metody klasy na dwie kategorie: „dla każdego do użytku” i „tylko do własnego użytku”.

Interfejsy pomagają jeszcze bardziej wzmocnić ten podział. Stworzymy specjalną „klasę do użytku przez wszystkich” oraz drugą klasę „tylko na własny użytek”, która odziedziczy pierwszą klasę. Oto mniej więcej jak by to wyglądało:

Zanim Po
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());
}

Podzieliliśmy naszą klasę na dwie części: interfejs i klasę , która dziedziczy interfejs . A co tu jest zaletą?

Wiele różnych klas może implementować (dziedziczyć) ten sam interfejs. I każdy może mieć swoje własne zachowanie. Na przykład ArrayList LinkedListsą dwie różne implementacje interfejsu List.

W ten sposób ukrywamy nie tylko różne implementacje, ale także samą klasę implementującą (ponieważ w kodzie potrzebujemy tylko interfejsu). To pozwala nam być bardzo elastycznymi: w trakcie działania programu możemy zastąpić jeden obiekt innym, zmieniając zachowanie obiektu bez wpływu na wszystkie klasy, które go używają.

Jest to bardzo potężna technika w połączeniu z polimorfizmem. Na razie nie jest oczywiste, dlaczego powinieneś to zrobić. Najpierw musisz zetknąć się z programami z dziesiątkami lub setkami klas, aby zrozumieć, że interfejsy mogą znacznie ułatwić Ci życie, niż bez nich.


3. Wielokrotne dziedziczenie

W Javie wszystkie klasy mogą mieć tylko jedną klasę nadrzędną. W innych językach programowania klasy mogą często mieć wiele klas nadrzędnych. Jest to bardzo wygodne, ale niesie ze sobą również wiele problemów.

Twórcy Javy doszli do kompromisu: zakazali wielokrotnego dziedziczenia klas, ale zezwolili na wielokrotne dziedziczenie interfejsów. Interfejs może mieć wiele interfejsów nadrzędnych. Klasa może mieć wiele interfejsów nadrzędnych, ale tylko jedną klasę nadrzędną.

Dlaczego zakazali wielokrotnego dziedziczenia klas, ale zezwolili na wielokrotne dziedziczenie interfejsów? Z powodu tak zwanego problemu dziedziczenia diamentów:

Wielokrotne dziedziczenie

Kiedy klasa B dziedziczy klasę A, nie wie nic o klasach C i D. Używa więc zmiennych klasy A według własnego uznania. Klasa C robi to samo: używa zmiennych klasy A, ale w inny sposób. A wszystko to skutkuje konfliktem w klasie D.

Spójrzmy na następujący prosty przykład. Powiedzmy, że mamy 3 klasy:

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

Klasa Data przechowuje valuezmienną. Jego klasa potomna XCoordinate używa tej zmiennej do przechowywania xwartości, a YCoordinateklasa potomna używa jej do przechowywania ywartości.

I to działa. Osobno. Ale jeśli chcemy, aby klasa XYCoordinates dziedziczyła zarówno klasy, XCoordinatejak i YCoordinate, otrzymamy zepsuty kod. Ta klasa będzie miała metody swoich klas przodków, ale nie będą działać poprawnie, ponieważ mają ten sam plik value variable.

Ale ponieważ interfejsy nie mogą mieć zmiennych, nie mogą mieć tego rodzaju konfliktów. W związku z tym dozwolone jest wielokrotne dziedziczenie interfejsów.