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 interface
kluczowego.
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();
}
- Zamiast
class
słowa kluczowego piszemyinterface
. - Zawiera tylko abstrakcyjne metody (nie pisz słowa
abstract
kluczowego) - W rzeczywistości interfejsy mają wszystkie
public
metody
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ą implements
sł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, ChessItem
klasa zawiera jedną abstrakcyjną metodę — draw()
.
Techniczne znaczenie słów kluczowych extends
i implements
jest 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 public
kluczowym. Jeśli chcesz, aby niektóre z tych metod były wywoływane tylko z poziomu Twojej klasy, musisz oznaczyć je słowem private
kluczowym. 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 |
---|---|
|
|
|
|
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
LinkedList
są 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:
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 value
zmienną. Jego klasa potomna XCoordinate używa tej zmiennej do przechowywania x
wartości, a YCoordinate
klasa potomna używa jej do przechowywania y
wartości.
I to działa. Osobno. Ale jeśli chcemy, aby klasa XYCoordinates dziedziczyła zarówno klasy, XCoordinate
jak 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.
GO TO FULL VERSION