Interfejs to kontrakt
Interfejs deklaruje, co klasa potrafi, nie mówiąc, jak. To lista sygnatur metod, które każda implementująca go klasa obiecuje spełnić. Chodzi o rozdzielenie zależności: kod może pracować z typem interfejsu i nie przejmować się tym, jaka konkretna klasa stoi za nim.
Circle implements Shape oznacza, że Circle musi zdefiniować area i perimeter. Gdy to zrobi, Circle jest Shape i można go przechowywać w zmiennej typu Shape.
Wiele klas, jeden typ
Siła interfejsów widać wtedy, gdy kilka niepowiązanych klas implementuje ten sam interfejs. Kod napisany pod Shape obsługuje je wszystkie:
Pętla nigdy nie pyta „czy to Circle, czy Rectangle?”. Po prostu wywołuje area() i ufa kontraktowi. Dodanie później klasy Triangle nie wymaga tu żadnej zmiany.
Implementowanie wielu interfejsów
Klasa rozszerza tylko jedną klasę, ale może implementować dowolną liczbę interfejsów. Tak Java bezpiecznie realizuje „wielokrotne dziedziczenie” zachowania:
Wypisz interfejsy po przecinku po implements. Klasa musi spełnić je wszystkie.
Metody default i static
Od Javy 8 interfejs może zawierać ciała metod. Metoda default daje klasom implementującym gotową implementację, którą dziedziczą za darmo (i mogą nadpisać):
Metody default pozwalają interfejsowi ewoluować: można dodać metodę, nie psując żadnej istniejącej implementacji. Metoda static interfejsu działa natomiast jak inne składowe statyczne: to funkcja pomocnicza wywoływana na samym interfejsie, na przykład Comparator.naturalOrder().
Stałe
Pola w interfejsie są niejawnie public static final: to stałe, a nie stan instancji:
interface Physics {
double GRAVITY = 9.81; // automatically public static final
}
Interfejsy tradycyjnie nie przechowują stanu poszczególnych obiektów. Brak pól instancji to część tego, co odróżnia je od klas.
Interfejsy funkcyjne
Interfejs z dokładnie jedną metodą abstrakcyjną to interfejs funkcyjny i możesz go zaimplementować wyrażeniem lambda zamiast całej klasy:
To podstawa lambd w Javie i typów z java.util.function (Function, Predicate, Supplier i pokrewnych). Adnotacja @FunctionalInterface jawnie pokazuje intencję i pozwala kompilatorowi pilnować zasady jednej metody.
Interfejs a klasa abstrakcyjna
Oba pozwalają programować pod abstrakcję, więc kiedy wybrać które?
- Interfejs: zdolność, którą mogą współdzielić niepowiązane klasy.
BirdiAirplanemogą być jednocześnieFlyable. Klasa może implementować wiele interfejsów. Brak stanu instancji. - Klasa abstrakcyjna: wspólny stan i kod blisko spokrewnionych klas.
CatiDogrozszerzająAnimal, dziedzicząc wspólne pola i częściowo zaimplementowane metody. Klasa rozszerza tylko jedną.
Popularny wzorzec łączy oba podejścia: interfejs definiuje kontrakt, a klasa abstrakcyjna implementuje powtarzalne części, więc konkretne podklasy uzupełniają tylko szczegóły.
Dalej: klasy abstrakcyjne
Interfejsy definiują zachowanie bez stanu. Klasy abstrakcyjne leżą pomiędzy interfejsami a pełnymi klasami: mogą deklarować niezaimplementowane metody i mieć pola oraz konstruktory. O tym jest następna strona.
Najczęściej zadawane pytania
Czym jest interfejs w Javie?
Interfejs to kontrakt: zestaw sygnatur metod (i stałych), które klasa obiecuje dostarczyć. Mówi, co klasa potrafi, a nie jak to robi. Klasa przyjmuje interfejs słowem kluczowym implements i musi dostarczyć ciało każdej metody abstrakcyjnej, którą interfejs deklaruje. Dzięki interfejsom niepowiązane ze sobą klasy można stosować zamiennie przez wspólny typ.
Czym różni się interfejs od klasy abstrakcyjnej w Javie?
Klasa może implementować wiele interfejsów, ale rozszerzać tylko jedną klasę (abstrakcyjną). Interfejsy tradycyjnie nie przechowują stanu instancji i tylko deklarują zachowanie, a klasa abstrakcyjna może mieć pola, konstruktory i częściowo zaimplementowaną logikę. Użyj interfejsu, by zdefiniować zdolność, którą może współdzielić kilka niepowiązanych klas. Użyj klasy abstrakcyjnej, by współdzielić stan i kod między blisko spokrewnionymi podklasami.
Czy interfejs w Javie może mieć ciała metod?
Tak, od Javy 8. Metody interfejsu oznaczone jako default mają ciało, które klasy implementujące dziedziczą (i mogą nadpisać), a metody static interfejsu dostarczają funkcje pomocnicze wywoływane na samym interfejsie. Zwykłe metody interfejsu nadal są abstrakcyjne, czyli to sama sygnatura, i klasy implementujące muszą je zdefiniować.