JavaRush /Blog Java /Random-PL /Interfejsy w Javie

Interfejsy w Javie

Opublikowano w grupie Random-PL
Cześć! Dzisiaj porozmawiamy o ważnym pojęciu w Javie – interfejsach. Słowo to prawdopodobnie jest Ci znane. Na przykład większość programów i gier komputerowych ma interfejsy. W szerokim znaczeniu interfejs to rodzaj „pilota”, który łączy dwie strony współdziałające ze sobą. Prostym przykładem interfejsu z życia codziennego jest pilot do telewizora. Łączy dwa obiekty, osobę i telewizor, i wykonuje różne zadania: zwiększa lub zmniejsza głośność, zmienia kanały, włącza i wyłącza telewizor. Jedna strona (osoba) musi uzyskać dostęp do interfejsu (nacisnąć przycisk pilota), aby druga strona mogła wykonać akcję. Na przykład, aby telewizor przełączył kanał na następny. W takim przypadku użytkownik nie musi znać urządzenia telewizora i sposobu, w jaki realizowany jest w nim proces zmiany kanału. Dlaczego interfejsy są potrzebne w Javie - 1Jedyne, do czego użytkownik ma dostęp, to interfejs . Głównym zadaniem jest uzyskanie pożądanego rezultatu. Co to ma wspólnego z programowaniem i Javą? Direct :) Tworzenie interfejsu jest bardzo podobne do tworzenia zwykłej klasy, tyle że zamiast słowa classpodajemy słowo interface. Przyjrzyjmy się najprostszemu interfejsowi Java i dowiedzmy się, jak działa i do czego jest potrzebny:
public interface Swimmable  {

     public void swim();
}
Stworzyliśmy interfejs Swimmable, który potrafi pływać . To coś w rodzaju naszego pilota, który ma jeden „przycisk”: metodą swim() jest „pływanie”. Jak możemy korzystać z tego „ pilota ”? W tym celu stosuje się metodę, tj. przycisk na naszym pilocie musi zostać zaimplementowany. Aby skorzystać z interfejsu, jego metody muszą być zaimplementowane przez niektóre klasy naszego programu. Wymyślmy klasę, której obiekty pasują do opisu „umie pływać”. Na przykład odpowiednia jest klasa kaczki Duck:
public class Duck implements Swimmable {

    public void swim() {
        System.out.println(„Kaczka, płyń!”);
    }

    public static void main(String[] args) {

        Duck duck = new Duck();
        duck.swim();
    }
}
Co tu widzimy? Klasa Duckjest powiązana z interfejsem Swimmableza pomocą słowa kluczowego implements. Jeśli pamiętasz, podobny mechanizm zastosowaliśmy do łączenia dwóch klas w dziedziczeniu, tyle że było tam słowo „ rozszerza ”. „ public class Duck implements Swimmable” dla przejrzystości można przetłumaczyć dosłownie: „klasa publiczna Duckimplementuje interfejs Swimmable”. Oznacza to, że klasa powiązana z interfejsem musi implementować wszystkie swoje metody. Uwaga: w naszej klasie, Duckpodobnie jak w interfejsie , Swimmableistnieje metoda swim(), a wewnątrz niej kryje się pewnego rodzaju logika. Jest to wymóg obowiązkowy. Gdybyśmy po prostu napisali „ public class Duck implements Swimmable” i nie utworzyli metody swim()w klasie Duck, kompilator dałby nam błąd: Duck nie jest abstrakcyjny i nie zastępuje abstrakcyjnej metody swim() w Swimmable. Dlaczego tak się dzieje? Jeśli wyjaśnimy błąd na przykładzie telewizora, okaże się, że dajemy osobie pilota z przyciskiem „zmień kanał” od telewizora, który nie potrafi zmieniać kanałów. W tym momencie naciskaj przycisk tyle, ile chcesz, nic nie będzie działać. Sam pilot nie zmienia kanałów: przekazuje jedynie sygnał do telewizora, w którym realizowany jest złożony proces zmiany kanału. Podobnie jest z naszą kaczką: musi umieć pływać, aby można było do niej dotrzeć za pomocą interfejsu Swimmable. Jeśli nie wie jak to zrobić, interfejs Swimmablenie połączy dwóch stron – osoby i programu. Osoba nie będzie w stanie użyć metody, swim()która sprawi, że obiekt Duckwewnątrz programu będzie pływał. Teraz widziałeś wyraźniej, do czego służą interfejsy. Interfejs opisuje zachowanie, jakie muszą mieć klasy implementujące ten interfejs. „Zachowanie” to zbiór metod. Jeśli chcemy stworzyć wielu komunikatorów, najłatwiej to zrobić tworząc interfejs Messenger. Co powinien umieć każdy posłaniec? W uproszczonej formie odbieraj i wysyłaj wiadomości.
public interface Messenger{

     public void sendMessage();

     public void getMessage();
}
A teraz możemy po prostu stworzyć nasze klasy komunikatorów, implementując ten interfejs. Sam kompilator „zmusi” nas do zaimplementowania ich wewnątrz klas. Telegram:
public class Telegram implements Messenger {

    public void sendMessage() {

        System.out.println(„Wysyłanie wiadomości do Telegrama!”);
    }

     public void getMessage() {
         System.out.println(„Czytanie wiadomości w telegramie!”);
     }
}
WhatsApp:
public class WhatsApp implements Messenger {

    public void sendMessage() {

        System.out.println(„Wysyłanie wiadomości WhatsApp!”);
    }

     public void getMessage() {
         System.out.println(„Czytanie wiadomości WhatsApp!”);
     }
}
Vibera:
public class Viber implements Messenger {

    public void sendMessage() {

        System.out.println(„Wysyłanie wiadomości do Viber!”);
    }

     public void getMessage() {
         System.out.println(„Czytanie wiadomości w Viber!”);
     }
}
Jakie korzyści to zapewnia? Najważniejszym z nich jest luźne sprzęgło. Wyobraźmy sobie, że projektujemy program, w którym będziemy zbierać dane klientów. Klasa Clientmusi posiadać pole wskazujące z jakiego komunikatora korzysta klient. Bez interfejsów wyglądałoby to dziwnie:
public class Client {

    private WhatsApp whatsApp;
    private Telegram telegram;
    private Viber viber;
}
Stworzyliśmy trzy pola, ale klient może spokojnie mieć tylko jednego komunikatora. Nie wiemy tylko który. A żeby nie pozostać bez komunikacji z klientem, trzeba „wcisnąć” do klasy wszystkie możliwe opcje. Okazuje się, że jeden lub dwa z nich zawsze tam będą nulli wcale nie są potrzebne, aby program działał. Zamiast tego lepiej skorzystać z naszego interfejsu:
public class Client {

    private Messenger messenger;
}
To jest przykład „luźnego połączenia”! Zamiast podawać w klasie konkretną klasę komunikatora Client, po prostu wspominamy, że klient ma komunikatora. Który zostanie określony w trakcie programu. Ale po co nam do tego interfejsy? Po co w ogóle dodano je do języka? Pytanie jest dobre i trafne! Ten sam wynik można osiągnąć stosując zwykłe dziedziczenie, prawda? Klasa Messengerjest klasą nadrzędną, a Viber, Telegrami WhatsAppsą spadkobiercami. Rzeczywiście, jest to możliwe. Ale jest jeden haczyk. Jak już wiesz, w Javie nie ma dziedziczenia wielokrotnego. Istnieje jednak wiele implementacji interfejsów. Klasa może implementować dowolną liczbę interfejsów. Wyobraźmy sobie, że mamy klasę Smartphone, która posiada pole Application– aplikację zainstalowaną na smartfonie.
public class Smartphone {

    private Application application;
}
Aplikacja i komunikator są oczywiście podobne, ale jednak to różne rzeczy. Komunikator może być zarówno mobilny, jak i stacjonarny, natomiast Aplikacja jest aplikacją mobilną. Zatem gdybyśmy zastosowali dziedziczenie, nie moglibyśmy dodać obiektu Telegramdo klasy Smartphone. W końcu klasa Telegramnie może dziedziczyć po Applicationi po Messenger! I udało nam się już to odziedziczyć po Messengeri dodać do klasy w tej formie Client. Ale klasa Telegrammoże z łatwością zaimplementować oba interfejsy! Dlatego w klasie Clientmożemy zaimplementować obiekt Telegramjako Messenger, a w klasie Smartphonejako Application. Oto jak to się robi:
public class Telegram implements Application, Messenger {

    //...metody
}

public class Client {

    private Messenger messenger;

    public Client() {
        this.messenger = new Telegram();
    }
}


public class Smartphone {

    private Application application;

    public Smartphone() {
        this.application = new Telegram();
    }
}
Teraz możemy korzystać z klasy Telegramwedług własnego uznania. Gdzieś będzie występował w roli Application, gdzieś w roli Messenger. Zapewne zauważyłeś już, że metody w interfejsach są zawsze „puste”, czyli nie mają implementacji. Powód tego jest prosty: interfejs opisuje zachowanie, a nie je implementuje. „Wszystkie obiekty klas, które implementują interfejs, Swimmablemuszą mieć możliwość pływania”: to wszystko, co mówi nam interfejs. Jak dokładnie będzie pływać ryba, kaczka czy koń, to jest pytanie do klas Fish, Ducki Horse, a nie do interfejsu. Podobnie jak zmiana kanału jest zadaniem telewizora. Na pilocie po prostu masz przycisk, który to umożliwia. Jednak Java8 ma ciekawy dodatek - metody domyślne. Na przykład twój interfejs ma 10 metod. 9 z nich jest zaimplementowanych różnie w różnych klasach, ale jeden jest zaimplementowany tak samo we wszystkich. Wcześniej, przed wydaniem Java8, metody wewnątrz interfejsów nie miały żadnej implementacji: kompilator natychmiast zgłaszał błąd. Teraz możesz to zrobić w ten sposób:
public interface Swimmable {

   public default void swim() {
       System.out.println("Pływać!");
   }

   public void eat();

   public void run();
}
Za pomocą słowa kluczowego defaultutworzyliśmy w interfejsie metodę z domyślną implementacją. Będziemy musieli zaimplementować dwie pozostałe metody eat()i run()sami we wszystkich klasach, które będą implementować metodę Swimmable. Nie ma potrzeby robić tego w przypadku metody swim(): implementacja będzie taka sama we wszystkich klasach. Swoją drogą, w poprzednich zadaniach natrafiłeś na interfejsy nie raz, choć sam tego nie zauważyłeś :) Oto oczywisty przykład: Dlaczego potrzebujemy interfejsów w Javie - 2Pracowałeś z interfejsami Listi Set! Dokładniej, z ich implementacjami - , ArrayListi innymi. Ten sam diagram pokazuje przykład, gdy jedna klasa implementuje kilka interfejsów jednocześnie. Na przykład implementuje interfejsy i (kolejkę dwustronną). Znasz także interfejs , a raczej jego implementacje - . Nawiasem mówiąc, na tym schemacie widać jedną cechę: interfejsy można dziedziczyć od siebie. Interfejs jest dziedziczony z , i jest dziedziczony z kolejki . Jest to konieczne, jeśli chcesz pokazać połączenie między interfejsami, ale jeden interfejs jest rozszerzoną wersją drugiego. Spójrzmy na przykład z interfejsem - kolejką. Nie przeglądaliśmy jeszcze kolekcji , ale są one dość proste i ułożone jak zwykła linia w sklepie. Możesz dodawać elementy tylko na końcu kolejki i usuwać je tylko od początku. Na pewnym etapie twórcy potrzebowali rozszerzonej wersji kolejki, aby można było dodawać i odbierać elementy z obu stron. Tak powstał interfejs - kolejka dwukierunkowa. Zawiera wszystkie metody zwykłej kolejki, ponieważ jest „rodzicem” kolejki dwukierunkowej, ale dodano nowe metody. LinkedListHashSetLinkedListListDequeMapHashMapSortedMapMapDequeQueueQueueQueueDeque
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION