W #main z 28 września pytaliśmy o różnice pomiędzy interfejsem i klasą abstrakcyjną — w czasach Javy 7 odpowiedź ta miałaby jedno zdanie i nie ‘zasłużyła by’ na osobny wpis. Java 8 zmieniła jednak sytuacje diametralnie — zachęcamy do lektury!
Klasy abstrakcyjne
W klasach abstrakcyjnych nie zmieniły się założenia w Javie 8 — klasa abstrakcyjna to taka klasa, która może mieć metody abstrakcyjne. Za moment wyjaśnimy sobie czym są owe metody dokładnie, ale jeszcze parę słów o klasach abstrakcyjnych — poza powyższym rządzą się prawie tymi samymi prawami, co zwykłe klasy — mogą dziedziczyć po innych, można po nich dziedziczyć i mogą mieć normalne metody oraz implementować interfejsy. Jedyne różnice związane z tym, że są one abstrakcyjne to takie, że nie możemy utworzyć obiektu tego typu oraz klasa nie może być jednocześnie abstract i final.
Metody abstrakcyjne są bardzo podobne do zwykłych metod interfejsów — zawierają jedynie sygnaturę metody, bez jej ciała. Oznacza to, że klasy dziedziczące muszą ‘określić’ ciało tej metody (po prostu ją przesłaniając) lub same muszą być wtedy zadeklarowane jako abstrakcyjne. Przykładowa klasa abstrakcyjna wygląda następująco:
public abstract class AbstractClass {
public void saySomething() {
System.out.println("Something");
}
public abstract void saySomethingUsefull();
}
Co ważne, klasa abstrakcyjna wcale nie musi mieć metod abstrakcyjnych! Może to być sposób na zablokowanie tworzenia instancji danej klasy, jak na poniższym przykładzie:
public abstract class BaseService {
protected String toJsonString() {
//...
}
protected String innaMetodaPomocnicza() {
//...
}
}
Interfejsy
Interfejsy w Javie 7 były pewnego rodzaju zbiorem sygnatur metod, bez ciała. Od klas abstrakcyjnych odróżniało je to, że nie mogły mieć ciała metod, dozwolone było za to wielokrotne dziedziczenie.
Java 8 wprowadza nowość w interfejsach, a dokładniej metody domyślne. Metody domyślne pozwalają zdefiniować ciało metody w interfejsie. Co więcej, zachowujemy możliwość wielokrotnego dziedziczenia!
Tutaj zastrzeżenie — ta nowa cecha języka może być bardzo łatwo nadużywana i nie powinna być stosowana do implementacji wielokrotnego dziedziczenia w Javie — nie do tego służy i nie powinna być do tego stosowana. Korzystanie z metod domyślnych może też wprowadzić zamęt wśród osób rozwijających Twój kod w przyszłości. Polecamy mail na grupie Javy, którego autorem jest Brian Goetz, jeden z architektów samej Javy — wprawdzie odpowiada on na specyficzne pytanie, bardzo dobrze opisuje cel i ideę metod domyślnych.
Przykładowy interfejs z metodą domyślną wygląda następująco:
public interface Prioritized {
public default int getPriority() {
return 0;
}
}
Pozwala on stosować ten interfejs jako ‘marker’, bez konieczności implementacji metody, która w większości przypadków wyglądałaby identycznie.
Zastrzeżenie: o ile powyższy fragment jest zgodny z celem, w jakim metody domyślne powstały, do określania priorytetów obiektów znacznie lepszym pomysłem jest skorzystanie z interfejsów, które już istnieją, jak Comparable czy Ordered (w Springu)
Mówiąc o metodach domyślnych warto poruszyć kolejną ważną rzecz — o ile Java dopuszcza wielokrotne dziedziczenie interfejsów, i jest to możliwe także z metodami domyślnymi, to w sytuacji konfliktu (dwa interfejsy posiadające metodę domyślną) wystąpi błąd kompilacji. Metody domyślne nie mogą też przysłaniać metod obiektów — metoda obiektu zawsze ‘wygrywa’ z metodą domyślną i to ona (metoda obiektu) jest uruchamiana.
Podsumowanie — jaka jest zatem różnica
Podsumowując, klasy abstrakcyjne mogą dziedziczyć tylko po jednej innej klasie, mogą dostarczać ciała metod i mogą określać metody o innej widoczności niż ‘public’. Interfejsy z kolei mogą być dziedziczone wielokrotnie, mogą dostarczać domyślne ciała metod (ale nie mogą przesłaniać innych metod) i dotyczą tylko metod o widoczności public.
Jak widzisz, odpowiedź na pytanie o różnice pomiędzy klasą abstrakcyjną a interfejsem nie jest już tak prosta ;) Warto o tym wiedzieć, jednak odradzam programowanie za pomocą metod domyślnych — możemy pewne elementy do nich ‘wyciągnąć’ już po napisaniu kodu, jako upiększenie i uproszczenie kodu, co pozwoli zminimalizować ryzyko nadużycia, ale nigdy, przenigdy nie stosuj tej cechy języka do obejścia problemów z projektem aplikacji i hierarchii klas!
Po bardziej szczegółowy opis odsyłamy do tutorialu Oracle.