Twitter: @grzegg
Kategoria: java, Tagi: - - .

Java [26] – Interfejsy w programowaniu obiektowym

Słowo „interfejs” kojarzy się zwykle np. z interfejsem graficznym albo tekstowym, czyli sposobem interakcji użytkownika z programem. W pewnym sensie podobną funkcję pełni interfejs w programowaniu obiektowym.

Interfejsy

W poprzedniej lekcji omawiałem pokrótce mechanizm dziedziczenia w programowaniu obiektowym a także klasy abstrakcyjne, które pozwalają na określenie czegoś w rodzaju zarysu klasy, który przyjmuje swoją ostateczną postać dopiero w klasie potomnej. Interfejsy z kolei są, mówiąc mało fachowym językiem, jeszcze bardziej abstrakcyjne od klas abstrakcyjnych. Struktura interfejsu przypomina strukturę klasy, ale nie zawiera zmiennych, które mogą zmieniać wartość (choć może zawierać stałe z przypisaną wartością) i zwykle nie posiada implementacji zadeklarowanych w niej metod.

Sens tworzenia interfejsów jest taki, że pozwalają one na opisanie tego jaką funkcjonalność powinne mieć klasy, ale nie jak ją mają realizować. Gdybyśmy chcieli odnieść tą technikę do organizmów, to interfejs opisywałby, że organizm ma: jeść, rozmnażać się, rosnąć itd. natomiast nie opisywałby co i jak ma jeść, jak się ma rozmnażać i jak przebiega jego wzrost. Te czynności realizują konkretne organizmy i inaczej wyglądają one u chorobotwórczej bakterii, inaczej u rośliny a jeszcze inaczej u drapieżnego zwierzęcia. Jednak w stosunku do każdego organizmu możemy oczekiwać tych opisanych w „interfejsie” czynności.

Spróbujmy zatem stworzyć w Javie interfejs deklarujących parę czynności życiowych organizmu a następnie stworzyć kilka klas implementujących ten interfejs.

Stwórz nowy projekt w NetBeans, nazwij go np. „Interfejsy” z klasą Interfejsy zawierającą metodę main. Następnie utwórz nowy interfejs, w ten sposób, że w miejscu gdzie zwykle podczas tworzenia nowego pliku (File -> New File) wybierasz „Java Class”, wybierz „Java Interface” i nazwij go Organizm. Powinien się pojawić odpowiedni plik z kodem:

Jak widać, zamiast słowa kluczowego class pojawiło się interface.

Teraz dodamy kilka deklaracji metod:

Jak widać, są to same deklaracje określające nazwę metody, rodzaj wartości zwracanej przez metodę oraz argumenty, które przyjmuje. Nie ma nawet pary nawiasów klamrowych.

Teraz zaimplementujmy te metody w kilku klasach. Najpierw stwórzmy klasę Mysz i zmodyfikujmy nieco jej nagłówek, dopisując po nazwie klasy implements Organizm:

Oznacza to, że klasa będzie implementowała interfejs o nazwie Organizm. Od razu NetBeans podkreśla na czerwono nazwę klasy i informuje nas, że Mysz nie jest klasą abstrakcyjną i nie nadpisaliśmy metody abstrakcyjnej rosnij(Double). Zgadza się, jeśli implementujemy jakiś interfejs to należy to zrobić względem wszystkich niezaimplementowanych metod.

Teraz stwórz kolejną klasę, Kot, także implementującą interfejs Organizm.
Następnie uzupełnij kodem klasy Mysz i Kot:

Zauważ, że obie klasy implementują trzy metody zadeklarowane w interfejsie Organizm. W klasie Mysz dopisana została ponadto metoda piszcz().

Teraz uzupełnijmy metodę main w klasie kodem, który pokaże parę aspektów wykorzystania interfejsów.

W zasadzie komentarze wyjaśniają kod. Zwróć uwagę na różnicę w sytuacji gdy deklarujemy obiekt jako typ Organizm, który jest interfejsem i typ właściwy dla klasy implementującej ten interfejs. W pierwszy przypadku nie możemy wykorzystać metod, które nie są zadeklarowane w interfejsie.

Teraz dopisz w klasie Interfejsy dodatkową metodę:


static public void nakarmOrganizm(Organizm stworzenie) {
   stworzenie.jedz();
}

oraz uzupełnij metodę main:


   nakarmOrganizm(mysz);
   nakarmOrganizm(kot);

Teraz widać sens używania interfejsów. Metoda nakarmOrganizm przyjmuje jako argument każdy obiekt, który został utworzony na podstawie klasy implementującej interfejs Organizm. Wykorzystuje metody, które są w tym interfejsie zadeklarowane i nie musi „wiedzieć” jakiego dokładnie typu są te obiekty, co więcej implementacja tych metod a także wynik ich działania może być zupełnie inny.

Implementacja wielu interfejsów na raz

Zapewne nie raz podczas lektury tej lekcji nasuwały Ci się porównania z klasami abstrakcyjnymi i ogólniej z mechanizmem dziedziczenia, który omówiłem poprzednim razem. Rzeczywiście podobieństw jest wiele. Do podstawowych różnic natomiast należy to, że o ile klasa pochodna może dziedziczyć bezpośrednio tylko po jednej klasie, to klasa może implementować na raz kilka interfejsów.

Stwórz kolejny interfejs Genetyczny

Zauważ, że znajduje się w nim deklaracja jednej metody, ale także dopisałem dwie stałe. Nie widać na pierwszy rzut oka, że są to stałe, poza zgodnym z konwencją użyciem wielkich liter, ale zmienne znajdujące się w interfejsach domyślnie są statyczne (modyfikator static) i stałe (modyfikator final).

Teraz zmodyfikujmy nieco klasę Mysz:

Zmodyfikowany został nagłówek klasy. Teraz implementuje ona zarówno interfejs Organizm jak i Genetyczny. Dodana została także implementacja metody zadeklarowanej w interfejsie Genetyczny oraz zaimportowana klasa java.util.Arrays.

Czas uzupełnić metodę main w klasie Interfejsy o kilka poleceń:


System.out.println("Materiał genetyczny myszy to " +
            myszka.MATERIAL_GENETYCZNY);
// w interfejsie pola są statyczne, nie musimy ich
// wywoływać z obiektów
System.out.println("Liczba rodzajów nukeotydów = " +
            Genetyczny.RODZAJE_NUKLEOTYDOW);
String[] sekwencja = {"A","C","C","A","G","A","G","G"};
myszka.wyswietlSekwencje(sekwencja);

Metody domyślne w interfejsach

Jak wspomniałem wcześniej interfejsy zasadniczo zawierają jedynie deklarację metod, bez wykonywalnego kodu. Wcześniej było to całkiem zabronione, ale wraz z Javą w wersji 8 pojawiła się możliwość umieszczania w interfejsach także implementacji metod. W takich wypadkach są to tzw. „metody domyślne”. Skąd takie określenie? Odpowiedź na pytanie znajdziemy w powodach, dla których w ostatniej (dotychczas) wersji naszego ulubionego języka programowania dodano taką opcję.
Twórcy Javy bardzo dbają o wsteczną kompatybiność języka. Oznacza to, że program oparty np. na Javie 5 powinien działać także gdy mamy zainstalowaną Javę 7 czy 8. Ale takie podejście stwarza też pewne problemy. Powiedzmy, że istnieje jakiś interfejs, który poszerzymy o nowe metody. Jak już wiemy klasa implementująca interfejs, musi zaimplementować wszystkie zadeklarowane w nim metody. Zatem, jeśli jakiś czas temu ktoś napisał klasę wykorzystującą omawiany interfejs, to po dodaniu do niego nowych metod, klasa przestanie działać, ponieważ nie posiada implementacji metod, które zostały napisane później. Umieszczenie domyślnej implementacji nowych metod rozwiązuje ten problem, stare klasy już nie muszą ich implementować.
Drugą zaletą stosowania metod domyślnych jest to, że mogą one być tworzone wtedy, gdy przewidujemy, że implementujące interfejs metody mogą korzystać tylko z części funkcjonalności oferowanej przez interfejs. W takim przypadku programista nie jest zmuszony do implementowania metod, których i tak nie będzie wykorzystywać.

Metoda domyślna musi otrzymać modyfikator default. Dopiszmy zatem do interfejsu Organizm metodę:


default void kopNore() {
   System.out.println("Kopię...");
}

Teraz dopiszemy jej implementację do klasy Mysz:


public void kopNore() {
   System.out.println("Kopię sobie norkę");
}

nie ma sensu jej implementować w klasie Kot gdyż jak wiadomo koty raczej raczej nor nie kopią.

Oczywiście w metodzie main klasy Interfejsy możemy wywołać metodę na odpowiednim obiekcie:


mycha.kopNore();

9 komentarzy Java [26] – Interfejsy w programowaniu obiektowym

  • way

    Bardzo dobrze opisane Interfejsy – w końcu zrozumiałem po co one są :) Mam tylko prośbę o rozwinięcie zapisu z metody main w linii 15: Organizm kotek = kot.rozmnozSie();
    Nie wiem czy dobrze rozumiem- następuje deklaracja zmiennej „kotek” typu „Organizm” (ze względu na implementacje do klasy „Kot” interfejsu „Organizm”) która wskazuje na metodę „rozmnozSie” obiektu „kot”, czyli zwraca obiekt „małykot” jednocześnie ustawiając wartość „maskota” na 500 ?
    Gdybyśmy nie stosowali interfejsów, to zapis maiłby postać: kotek = kot.rozmnozSie(); ?
    Ogólnie mam problem ze zwracaniem obiektów i używaniem ich jako argumentów, stąd moje wątpliwości.

    • Grzegorz

      > Bardzo dobrze opisane Interfejsy – w końcu zrozumiałem po co one są :)

      Ciesze się :-)

      >Mam tylko prośbę o rozwinięcie zapisu z metody main w linii 15: Organizm kotek = kot.rozmnozSie();
      > Nie wiem czy dobrze rozumiem- następuje deklaracja zmiennej „kotek” typu „Organizm”

      raczej: obiektu kotek typu Organizm.

      >(ze względu na implementacje do klasy „Kot” interfejsu „Organizm”) która wskazuje na metodę
      >„rozmnozSie” obiektu „kot”, czyli zwraca obiekt „małykot” jednocześnie ustawiając wartość
      > „maskota” na 500 ?

      O ile dobrze rozumiem pytanie to tak :-)

      > Gdybyśmy nie stosowali interfejsów, to zapis maiłby postać: kotek = kot.rozmnozSie(); ?

      Np.:
      Kot kotek = kot.rozmnozSie();
      Klasa „Kot” po prostu byłaby samodzielną klasą.

      Pozdrawiam
      G.

  • Piotr

    Mam pytanie odnośnie linii 26 z metody main:
    // Stosujemy kasting z typu Organizm na Mysz
    Mysz mycha = (Mysz) mysz.rozmnozSie();
    mycha.piszcz();
    Po co jest tutaj to rzutowanie, skoro można użyć bezpośrednio tego typu bez rzutowania z metodą piszcz. Coś źle zrozumiałem?
    Nie zauważyłem, żeby wcześnie ta zmienna była typu Organizm.

    • Grzegorz

      Obiekt „mysz” jest typu „Organizm” (linia 7) a nie typu „Mysz”, zatem metoda „rozmnozSie” zwraca obiekt typu „Object”. Dlatego jeśli chcemy otrzymać obiekt typu „Mysz”, trzeba użyć kastingu.

  • Piotr

    W klasie Interfejsy w linii 12 jest:
    System.out.println(„Kot je: ” + mysz.jedz());
    a powinno być:
    System.out.println(„Kot je: ” + kot.jedz());

    z kolei w linii 15 (u mnie wyświetlało jako błąd):
    Organizm kotek = kot.rozmnozSie();
    metoda rozmnozSie() zwraca Object, który należałoby zrzutować na Organizm, czyli:
    Organizm kotek = (Organizm) kot.rozmnozSie();

  • Paul

    Czy mógłby ktoś wytłumaczyć dlaczego w klasie Mysz w linijce 16 po public jest typ Mysz zamiast Object jak było zadeklarowane w interfejsie Organizm (linia 5)? (Podobnie w klasie Kot w linii 13?)
    ” public Mysz rozmnozSie() { ”

    Kolejne pytanie dotyczy klasy Interfejsy linia 15
    „Organizm kotek = (Organizm)kot.rozmnozSie(); ” – proszę o łopatologiczne wyjaśnienie bo siedzę nad tym kolejny dzień i nie bardzo rozumiem…

    • Grzegorz

      „Czy mógłby ktoś wytłumaczyć dlaczego w klasie Mysz w linijce 16 po public jest typ Mysz zamiast Object jak było zadeklarowane w interfejsie Organizm (linia 5)? (Podobnie w klasie Kot w linii 13?)
      ” public Mysz rozmnozSie() { ””

      Klasa Mysz implementuje interfejs o nazwie Organizm.
      Obiekt typu Mysz „rozmnaża się” zwracając obiekt typu Mysz, który oprócz metod zadeklarowanych w interfejsie Organizm (i wszystkich obiektów, których charakterystyka jest zdefiniowana w Object) zawiera dodatkową metodę piszcz().
      Analogicznie obiekt typu Kot zwraca „kota”.
      Interfejs Organizm określa tylko, że metoda rozmnozSie() zwraca obiekt, w klasach Mysz i Kot „doprecyzowujemy”, jakiego rodzaju jest obiekt.

      ” Kolejne pytanie dotyczy klasy Interfejsy linia 15
      „Organizm kotek = (Organizm)kot.rozmnozSie(); ” – proszę o łopatologiczne wyjaśnienie bo siedzę nad tym kolejny dzień i nie bardzo rozumiem…”

      Jeśli utworzymy „kotka” w ten sposób:
      Organizm kotek = kot.rozmnozSie();
      w obiekcie kotek, możemy używać tylko metod zadeklarowanych w interfejsie „Organizm”.
      Proszę zwrócić uwagę na dalszy fragment kodu, kiedy tworzymy „myszkę”:
      Mysz myszka = new Mysz(6);
      Teraz możemy wykorzystywać także metody charakterystyczne dla klasy Mysz.

      Analogicznie, można zrobić z „kotkiem”

      Mam nadzieję, że pomogłem.
      Pozdrawiam
      Grzegorz Góralski

  • Paul

    Rozkładając na czynniki pierwsze jeszcze raz ten fragment kodu:
    Organizm kotek = kot.rozmnozSie();

    popraw mnie proszę czy dobrze rozumiem: tj utworzenie nowego obiektu o nazwie kotek typu Organizm i przypisaniu mu metody rozmnozSie(), która zwraca typ Kot. Tylko dlaczego nie można napisać po prostu kot.rozmnozSie();
    przecież kot w linii 8 jest zadeklarowany jako typ Organizm? Po co ten kotek??

    • Grzegorz

      Pisząc tak:
      Organizm kotek = kot.rozmnozSie();
      Otrzymujemy obiekt kotek, który później można wykorzystać w programie.
      Samo kot.rozmnozSie(); nie spowoduje utworzenia obiektu, nadającego się do użytku, obiekt kot nie przekaże go skutecznie do metody main();
      Inaczej: skąd metoda main() miałaby „wiedzieć” jak nazywa się obiekt?

      BTW oczywiście jeśli chcemy później używać metod charakterystycznych dla klasy Kot (a nie tylko zdefiniowanych w interfejsie Organizm), trzeba napisać:
      Kot kotek = kot.rozmnozSie();

Leave a Reply