Czym są interfejsy w Javie?
Interfejs w Javie to kontrakt, który mówi, co klasa może zrobić, ale nie informuje, jak ma to wykonać. Interfejsy Java definiujemy zawsze w osobnych plikach, których nazwa odpowiada nazwie interfejsu. Dzięki temu interfejs można implementować wielokrotnie, a klasom nie musimy nadawać za każdym razem tych samych metod. Pojęcie interfejsu jest kluczowe, by zrozumieć schemat działania Javy. To jedno z najważniejszych narzędzi, które daje ogromne możliwości i warto jest zrozumieć je na samym początku nauki.
Kolejnym ważnym pojęciem są, jeśli chodzi o język Java, interfejsy funkcyjne. Mają one tylko jedną metodę abstrakcyjną. Jest to metoda, co do której jeszcze nie potrafimy stwierdzić, jaka będzie jej implementacja (lub nie chcemy zbyt szybko tego robić), ale spodziewamy się, że powinna wystąpić w zestawie metod każdej klasy w tym interfejsie. Przykładem może być interfejs funkcyjny Comparator, który umożliwia sortowanie kolekcji – wiemy, że np. pracowników będziemy chcieli w przyszłości sortować, ale jeszcze nie będziemy tego definiować. Dzięki temu w późniejszym czasie możemy wybrać czy sortujemy po imieniu, po nazwisku czy po wysokości wypłat.
Prosty przykład interfejsu
Spójrzmy na przykładowy interfejs pojazd. W pliku będzie on posiadał metody – np. ruch i stop. Podczas identyfikowania klas w Javie, zaimplementujemy gotowy interfejs. Wyobraźmy sobie klasy motocykl i auto, do których zaimplementujemy interfejs pojazd. Dzięki temu mamy pewność, że motocykl i auto mogą poruszać się dzięki metodzie ruch i zatrzymać się dzięki metodzie stop.
Są to ogólne metody, które są prawidłowe dla każdego pojazdu. W praktyce programistycznej definicja takiego kontraktu wygląda następująco: używamy słowa kluczowego interface, a klasy, które chcą z niego skorzystać, deklarują to słowem implements. Dzięki temu kompilator pilnuje, abyśmy w klasie Samochod na pewno opisali, jak konkretnie wygląda ruch (np. poprzez uruchomienie silnika spalinowego) i hamowanie. Oczywiście oprócz tych narzuconych metod, każda klasa może mieć swoje indywidualne funkcje, jak np. parkowanie tyłem czy drift.
Są to ogólne metody, które są prawidłowe dla każdego pojazdu. Oczywiście oprócz tych metod, każda klasa może mieć indywidualne metody, np. motocykl może mieć metody jazda na jednym kole oraz pochylenie motocykla, a auto może mieć metody parkowanie tyłem lub drift na śniegu. Do interfejsu pojazd możemy dodać dowolną ilość klas i zawsze będą one spełniały warunki, które są określone w metodach interfejsu. Dzięki temu zaoszczędzimy mnóstwo czasu, ponieważ nie musimy do każdego pojazdu z osobna zapisywać tych samych metod.
Wskazówka dla profesjonalistów: Stosowanie interfejsów jest fundamentem zasady "Programuj do interfejsu, nie do implementacji". Pozwala to na łatwą zamianę jednej części systemu na drugą (np. zmianę bazy danych z MySQL na PostgreSQL) bez konieczności modyfikowania całego kodu aplikacji, o ile obie klasy spełniają ten sam kontrakt.
Rewolucja w Java 8: Metody domyślne i statyczne
Wspomnieliśmy, że Java 8 przyniosła spore zmiany. Przed jej wprowadzeniem interfejsy mogły zawierać wyłącznie „puste” deklaracje metod. Jeśli dodaliśmy nową metodę do interfejsu, wszystkie klasy go implementujące przestawały działać, dopóki programista ich nie zaktualizował.
Rozwiązaniem stały się metody domyślne (słowo kluczowe default). Pozwalają one dopisać gotową logikę bezpośrednio w interfejsie. Jeśli klasa nie zdefiniuje własnej wersji tej metody, po prostu skorzysta z tej domyślnej. Z kolei metody statyczne w interfejsach działają jak narzędzia pomocnicze, do których mamy dostęp bez tworzenia obiektu danej klasy. To sprawia, że interfejsy stały się znacznie bardziej elastyczne i potężne niż w starszych wersjach języka.
Dlaczego interfejsy w Javie są potrzebne?
Interfejsów w Javie używamy przede wszystkim do hermetyzacji obiektów, które są tworzone w oparciu o klasy zaimplementowane do wspólnego interfejsu. Pozwala to uniknąć błędów w programie i dużej ilości pracy (wystarczy, że programista przygotuje jeden interfejs Java ze zbiorem dokładnych metod i nie będzie musiał ich implementować każdej klasie z osobna). Hermetyzacja również ograniczy obiektowi dostęp do danych i metod innych klas, co chroni przed błędami.
Na przykład w programie obsługującym lotnisko, które ma w swojej flocie zarówno pojazdy – samochody techniczne oraz samoloty, pracownik nie pomyli się, bo nie może samochodowi technicznemu (pojazd) dać zadania lot, które jest zarezerwowane dla samolotu.
Dlaczego więc potrzebujemy interfejsów w Javie? Gdyż to dzięki nim powstaje mniejsza ilość kodu, a to spore ułatwienie dla programisty i wygodna możliwość implementacji w każdej chwili nowego obiektu do istniejącego interfejsu bez niepotrzebnego definiowania po raz kolejny tych samych metod.
Ile interfejsów możemy dodać do jednej klasy?
W programie napisanym w języku Java ilość klas i interfejsów jest nieograniczona. A co, jeśli chcemy zaimplementować do jednej klasy wiele interfejsów? To oczywiście możliwe, jednak klasa musi wtedy implementować każdą metodę każdego interfejsu. Oznacza to, że klasa zaimplementowana do interfejsów pojazd i samolot musi być rzeczywiście obiektem, który potrafi spełnić wszystkie metody obydwu interfejsów – musi jeździć i hamować na drogach oraz musi latać – w teorii coś takiego stworzyć się da.
Co mogą rozszerzać klasy, a co interfejsy – podstawy dziedziczenia
Najważniejszą zasadą rozszerzania klas w języku Java jest to, że klasa może rozszerzać tylko jedną nadklasę. Przykład: klasa to zawodnik (w drużynie piłkarskiej), jeśli chcemy rozszerzyć tę klasę do klasy kapitan – możemy to zrobić komendą extends. Oznacza to, że kapitan dziedziczy wszystkie metody klasy zawodnik, bo jest jej członkiem, jednak jest również kapitanem (pełni dodatkową funkcję, równolegle do bycia zawodnikiem).
Inaczej działa to z interfejsami, które mogą dowolnie rozszerzać inne interfejsy przy użyciu słowa extends. W przeciwieństwie do klas, jeden interfejs może rozszerzać wiele innych interfejsów jednocześnie. Na przykład, przygotowując oprogramowanie dla ZOO, możemy stworzyć interfejs Padlinozerca, który rozszerza jednocześnie interfejsy Miesozerny oraz Ladowy. Dzięki temu każda klasa implementująca Padlinozerce będzie musiała posiadać metody wynikające z obu tych „rodziców”.
Interfejs a klasa abstrakcyjna – co wybrać?
To jedno z najczęstszych pytań na początku nauki. Choć oba rozwiązania pozwalają definiować metody bez implementacji, różnią się celem. Klasa abstrakcyjna opisuje, czym obiekt jest (np. Ptak), natomiast interfejs opisuje, co obiekt potrafi robić (np. Latający).
Ważną różnicą techniczną jest to, o czym już wspomnieliśmy: klasa może dziedziczyć tylko po jednej innej klasie, ale może implementować wiele interfejsów. Dlatego interfejsy są lepszym wyborem, gdy chcemy dodać konkretną cechę (zachowanie) do wielu niespokrewnionych ze sobą klas.
Podsumowanie
Dzięki zastosowaniu interfejsów programy pisane w języku Java mają krótszy kod. W praktyce oznacza to, że interfejsy skracają czas pracy programistów i ułatwiają wiele czynności – wystarczy raz zdefiniować metody w pliku interfejsu, by nie trzeba było tego robić pojedynczo każdej klasie (których może być ogromna ilość) – ułatwia to pracę zawodowcom, ale również edukację. Jeśli dopiero rozpoczynasz naukę Javy, to warto postawić na wersję Java 8, ponieważ to właśnie w ósemce nastąpiły zmiany w interfejsach, które są obecne do dzisiaj.
Interfejsy funkcyjne Java 8 pozwalają na dodawanie definicji metod, we wcześniejszych wersjach można było dodać jedynie samą deklarację abstrakcyjnej metody. Pamiętaj również, że pomimo upływu czasu i obecności wersji Java 19 – to Java 8 jest nadal jedną z popularniejszych wersji.
Najczęściej zadawane pytania (FAQ)
Czy interfejs może mieć konstruktor?
Nie, interfejsy w Javie nie mogą mieć konstruktorów. Służą one do definiowania zachowań, a nie do tworzenia stanu obiektu. Obiekty tworzymy zawsze w oparciu o konkretne klasy, które te interfejsy implementują.
Czy wszystkie metody w interfejsie są publiczne?
Tak, domyślnie każda metoda w interfejsie jest traktowana jako public. Nawet jeśli nie wpiszemy tego słowa przed nazwą metody, Java przyjmie, że jest ona dostępna dla każdego, kto zechce zaimplementować ten kontrakt.
Czy interfejs może posiadać zmienne?
Interfejs może posiadać pola, ale są one automatycznie traktowane jako stałe (public static final). Oznacza to, że nie możemy zmieniać ich wartości w trakcie działania programu – zazwyczaj służą do przechowywania stałych konfiguracji wspólnych dla wielu klas.