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.
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 |
---|---|
|
Odpowiada zdolności do poruszania się |
|
Odpowiada zdolności do jazdy konnej |
|
Odpowiada zdolności do przenoszenia rzeczy |
|
Klasa Wheel może się poruszać |
|
Klasa Car może się poruszać, jeździć na niej i przewozić rzeczy |
|
Klasa Skateboard moż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 |
---|---|
|
zdolność CanSpeak . Ten interfejs rozumie polecenie to speak , co oznacza, że ma odpowiednią metodę. |
|
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.
|
|
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. default
Implementacja 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 abstract
kluczowym. 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, default
należ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ę InputStream
i OutputStream
klasy 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ż Tom
klasa nie implementuje meow()
metody.
GO TO FULL VERSION