#Praktyczna Java. Interfejsy, metody domyślne i klasy abstrakcyjne

By 5 October 2015 Praktyczna Java

W #main z 28 wrześ­nia pytal­iśmy o różnice pomiędzy inter­fe­jsem i klasą abstrak­cyjną — w cza­sach Javy 7 odpowiedź ta miała­by jed­no zdanie i nie ‘zasłużyła by’ na osob­ny wpis. Java 8 zmieniła jed­nak sytu­acje diame­tral­nie — zachę­camy do lektury!

Klasy abstrakcyjne

W klasach abstrak­cyjnych nie zmieniły się założe­nia w Javie 8 — klasa abstrak­cyj­na to taka klasa, która może mieć metody abstrak­cyjne. Za moment wyjaśn­imy sobie czym są owe metody dokład­nie, ale jeszcze parę słów o klasach abstrak­cyjnych — poza powyższym rządzą się praw­ie tymi samy­mi prawa­mi, co zwykłe klasy — mogą dziedz­iczyć po innych, moż­na po nich dziedz­iczyć i mogą mieć nor­malne metody oraz imple­men­tować inter­fe­jsy. Jedyne różnice związane z tym, że są one abstrak­cyjne to takie, że nie może­my utworzyć obiek­tu tego typu oraz klasa nie może być jed­nocześnie abstract i final.

Metody abstrak­cyjne są bard­zo podob­ne do zwykłych metod inter­fe­jsów — zaw­ier­a­ją jedynie syg­naturę metody, bez jej ciała. Oznacza to, że klasy dziedz­iczące muszą ‘określić’ ciało tej metody (po pros­tu ją przesła­ni­a­jąc) lub same muszą być wtedy zadeklarowane jako abstrak­cyjne. Przykład­owa klasa abstrak­cyj­na wyglą­da następująco:

public abstract class AbstractClass {
    public void saySomething() {
        System.out.println("Something");
    }

    public abstract void saySomethingUsefull();
}

Co ważne, klasa abstrak­cyj­na wcale nie musi mieć metod abstrak­cyjnych! 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

Inter­fe­jsy w Javie 7 były pewnego rodza­ju zbiorem syg­natur metod, bez ciała. Od klas abstrak­cyjnych odróż­ni­ało je to, że nie mogły mieć ciała metod, doz­wolone było za to wielokrotne dziedziczenie.

Java 8 wprowadza nowość w inter­fe­jsach, a dokład­niej metody domyślne. Metody domyślne pozwala­ją zdefin­iować ciało metody w inter­fe­jsie. Co więcej, zachowu­je­my możli­wość wielokrot­nego dziedziczenia!

Tutaj zas­trzeże­nie — ta nowa cecha języ­ka może być bard­zo łat­wo naduży­wana i nie powin­na być stosowana do imple­men­tacji wielokrot­nego dziedz­iczenia w Javie — nie do tego służy i nie powin­na być do tego stosowana. Korzys­tanie z metod domyśl­nych może też wprowadz­ić zamęt wśród osób rozwi­ja­ją­cych Twój kod w przyszłoś­ci. Pole­camy mail na grupie Javy, którego autorem jest Bri­an Goetz, jeden z architek­tów samej Javy — wprawdzie odpowia­da on na specy­ficzne pytanie, bard­zo dobrze opisu­je cel i ideę metod domyślnych.

Przykład­owy inter­fe­js z metodą domyśl­ną wyglą­da następująco:

public interface Prioritized {
    public default int getPriority() {
        return 0;
    }
}

Pozwala on stosować ten inter­fe­js jako ‘mark­er’, bez koniecznoś­ci imple­men­tacji metody, która w więk­szoś­ci przy­pad­ków wyglą­dała­by identycznie.

Zas­trzeże­nie: o ile powyższy frag­ment jest zgod­ny z celem, w jakim metody domyślne pow­stały, do określa­nia pri­o­ry­tetów obiek­tów znacznie lep­szym pomysłem jest sko­rzys­tanie z inter­fe­jsów, które już ist­nieją, jak Com­pa­ra­ble czy Ordered (w Springu)

Mówiąc o meto­dach domyśl­nych warto poruszyć kole­jną ważną rzecz — o ile Java dopuszcza wielokrotne dziedz­icze­nie inter­fe­jsów, i jest to możli­we także z meto­da­mi domyśl­ny­mi, to w sytu­acji kon­flik­tu (dwa inter­fe­jsy posi­ada­jące metodę domyśl­ną) wys­tąpi błąd kom­pi­lacji. Metody domyślne nie mogą też przysła­ni­ać metod obiek­tów — meto­da obiek­tu zawsze ‘wygry­wa’ z metodą domyśl­ną i to ona (meto­da obiek­tu) jest uruchamiana.

Podsumowanie — jaka jest zatem różnica

Pod­sumowu­jąc, klasy abstrak­cyjne mogą dziedz­iczyć tylko po jed­nej innej klasie, mogą dostar­czać ciała metod i mogą określać metody o innej widocznoś­ci niż ‘pub­lic’. Inter­fe­jsy z kolei mogą być dziedz­ic­zone wielokrot­nie, mogą dostar­czać domyślne ciała metod (ale nie mogą przesła­ni­ać innych metod) i doty­czą tylko metod o widocznoś­ci public.

Jak widzisz, odpowiedź na pytanie o różnice pomiędzy klasą abstrak­cyjną a inter­fe­jsem nie jest już tak pros­ta ;) Warto o tym wiedzieć, jed­nak odradzam pro­gramowanie za pomocą metod domyśl­nych — może­my pewne ele­men­ty do nich ‘wyciągnąć’ już po napisa­niu kodu, jako upięk­sze­nie i uproszcze­nie kodu, co poz­woli zmin­i­mal­i­zować ryzyko naduży­cia, ale nigdy, przenigdy nie sto­suj tej cechy języ­ka do obe­jś­cia prob­lemów z pro­jek­tem aplikacji i hier­ar­chii klas!

Po bardziej szczegółowy opis odsyłamy do tuto­ri­alu Ora­cle.