1. Zdolności

Aby lepiej zrozumieć zalety interfejsów i możliwości ich wykorzystania, musimy porozmawiać o bardziej abstrakcyjnych rzeczach.

Klasa zwykle modeluje konkretny obiekt. Interfejs w mniejszym stopniu odpowiada przedmiotom, a bardziej ich zdolnościom lub rolom.

Istota interfejsów

Na przykład rzeczy takie jak samochody, rowery, motocykle i koła są najlepiej reprezentowane jako klasy i obiekty. Ale ich umiejętności — takie jak „Mogę jeździć”, „Mogę przewozić ludzi”, „Mogę stać” — są lepiej przedstawiane jako interfejsy. Oto kilka przykładów:

Kod Opis
interface CanMove
{
   void move(String newLocation);
}
Odpowiada zdolności do poruszania się
interface Rideable
{
   void ride(Passenger passenger);
}
Odpowiada zdolności do jazdy konnej
interface CanTransport
{
   void addStuff(Object stuff);
   Object removeStuff();
}
Odpowiada zdolności do przenoszenia rzeczy
class Wheel implements CanMove
{
   ...
}
Klasa Wheelmoże się poruszać
class Car implements CanMove, Rideable, CanTransport
{
   ...
}
Klasa Carmoże się poruszać, jeździć na niej i przewozić rzeczy
class Skateboard implements CanMove, Rideable
{
   ...
}
Klasa Skateboardmoże się poruszać i jeździć


2. Role

Interfejsy znacznie upraszczają życie programisty. Bardzo często program ma tysiące obiektów, setki klas, ale zaledwie kilkadziesiąt interfejsów , czyli ról . Jest kilka ról, ale istnieje wiele sposobów ich łączenia (klas).

Chodzi o to, że nie musisz pisać kodu w każdej klasie, aby wchodzić w interakcje z każdą inną klasą. Wystarczy wejść w interakcję z ich rolami (interfejsami).

Wyobraź sobie, że jesteś trenerem zwierząt. Każde ze zwierząt, z którymi pracujesz, może mieć kilka różnych zdolności. Wdajesz się w przyjacielską kłótnię z sąsiadem o to, czyje zwierzęta mogą hałasować. Aby załatwić sprawę, po prostu ustaw wszystkie zwierzaki, które potrafią „mówić”, i wydaj im komendę: Mów!

Nie obchodzi cię, jakiego rodzaju są zwierzętami ani jakie inne zdolności mają. Nawet jeśli potrafią zrobić potrójne salto w tył. W tym konkretnym momencie interesuje Cię tylko ich umiejętność mówienia głośno. Oto jak by to wyglądało w kodzie:

Kod Opis
interface CanSpeak
{
   void speak();
}
zdolność CanSpeak. Ten interfejs rozumie polecenie to speak, co oznacza, że ​​ma odpowiednią metodę.
class Cat implements CanSpeak
{
   void speak()
   {
      println("MEOW");
   }
}

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

class Fish
{
   ...
}
Zwierzęta posiadające tę cechę.

Dla ułatwienia zrozumienia podaliśmy nazwy klas w języku angielskim. Jest to dozwolone w Javie, ale jest wysoce niepożądane.













Nasz Fishnie posiada możliwości mówienia (nie implementuje interfejsu 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();
      }
   }
}
A jak wydamy im polecenie?

Gdy liczba klas w Twoich programach osiągnie tysiące, nie będziesz mógł żyć bez interfejsów. Zamiast opisywać interakcję tysięcy klas, wystarczy opisać interakcję kilkudziesięciu interfejsów — to znacznie upraszcza życie.

A w połączeniu z polimorfizmem podejście to jest generalnie miażdżącym sukcesem.



3. defaultImplementacja metod interfejsowych

Klasy abstrakcyjne mogą mieć zmienne i implementacje metod, ale nie mogą mieć wielokrotnego dziedziczenia. Interfejsy nie mogą mieć zmiennych ani implementacji metod, ale mogą mieć wielokrotne dziedziczenie.

Sytuację przedstawia poniższa tabela:

Zdolność/właściwość Klasy abstrakcyjne Interfejsy
Zmienne
Implementacja metody
Wielokrotne dziedziczenie

Tak więc niektórzy programiści naprawdę chcieli, aby interfejsy miały możliwość implementacji metod. Ale możliwość dodania implementacji metody nie oznacza, że ​​zawsze będzie ona dodawana. Dodaj go, jeśli chcesz. Albo jeśli nie, to nie.

Ponadto problemy z wielokrotnym dziedziczeniem wynikają przede wszystkim ze zmiennych. W każdym razie tak postanowili i zrobili. Począwszy od JDK 8, Java wprowadziła możliwość dodawania implementacji metod do interfejsów.

Oto zaktualizowana tabela (dla JDK 8 i nowszych wersji):

Zdolność/właściwość Klasy abstrakcyjne Interfejsy
Zmienne
Implementacja metody
Wielokrotne dziedziczenie

Teraz w przypadku klas abstrakcyjnych oraz interfejsów można deklarować metody z implementacją lub bez. I to jest doskonała wiadomość!

W klasach abstrakcyjnych metody bez implementacji muszą być poprzedzone słowem abstractkluczowym. Nie musisz nic dodawać przed metodami z implementacją. W interfejsach jest odwrotnie. Jeśli metoda nie ma implementacji, nie należy nic dodawać. Ale jeśli istnieje implementacja, defaultnależy dodać słowo kluczowe.

Dla uproszczenia przedstawiamy te informacje w poniższej tabeli:

Zdolność/właściwość Klasy abstrakcyjne Interfejsy
Metody bez implementacji abstract
Metody z implementacją default

Problem

Korzystanie z interfejsów, które mają metody, może znacznie uprościć duże hierarchie klas. Na przykład abstrakcję InputStreami OutputStreamklasy można zadeklarować jako interfejsy! Dzięki temu możemy z nich korzystać znacznie częściej i wygodniej.

Ale na świecie są już dziesiątki milionów (miliardów?) klas Java. A jeśli zaczniesz zmieniać standardowe biblioteki, możesz coś zepsuć. Jak wszystko! 😛

Aby nie uszkodzić przypadkowo istniejących programów i bibliotek, zdecydowano, że implementacje metod w interfejsach będą miały najniższy priorytet dziedziczenia .

Na przykład, jeśli jeden interfejs dziedziczy inny interfejs, który ma metodę, a pierwszy interfejs deklaruje tę samą metodę, ale bez implementacji, wówczas implementacja metody z odziedziczonego interfejsu nie dotrze do dziedziczącego interfejsu. Przykład:

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

Kod nie skompiluje się, ponieważ Tomklasa nie implementuje meow()metody.