Interfejsy w Javie

Poprzednio omówiliśmy takie elementy dziedziczenia jak hierarchia, klasy abstrakcyjne oraz rzutowanie obiektów.

Teraz poszerzymy nasz warsztat programistyczny o kolejny ważny element, a są nim interfejsy (ang. interface). Słowo interfejs ma wiele znaczeń, ale w poniższym opracowaniu należy je rozumieć jako szereg wymagań jaki musi spełnić klasa, aby móc zapewnić, że implementuje interfejs (co powinna robić, aby być z nim zgodna), ale nie mówiąc zupełnie nic na temat sposobu w jaki to ma się odbywać.

Własny interfejs

Aby zaimplementować własny interfejs należy użyć słowa kluczowego interface.

Metody w interfejsach

Spójrzmy na powyższy fragment kodu pokazujący prostą definicję interfejsu. Zawiera on jedną metodę, której możemy powiedzieć, że zwraca typ int oraz jej argumentem jest zmienna typu int. Treść metody znajduje się dopiero w klasie implementującej ten interfejs.

Wyjątkiem są tzw. metody domyślne, których deklaracja poprzedza słowo kluczowe default.

Wszystkie metody wewnątrz interfejsu są publiczne i nie trzeba pisać modyfikatorów dostępu, natomiast w implementacji w klasie zasady są niezmienne i jeśli metoda ma być publiczna należy użyć słowa kluczowego public.

Zwróćmy jednak uwagę na metodę getInt(). Jest prywatna dla interfejsu. Możliwe jest zadeklarowanie takiej metody, jednakże musi zostać wykorzystana w innej metodzie wewnątrz interfejsu.

Pola w interfejsach

W interfejsach można definiować stałe. Tak samo jak w przypadku klas robimy to przy pomocy słowa kluczowego final. Nie można natomiast umieścić w interfejsie pól obiektowych ani prostych zmiennych.

Interfejs Comparable

Wracając do przykładu z poprzedniej części szkolenia zapewniliśmy, że wszystkie obiekty należące do podklas typu Żywność mają jakąś kaloryczność. Przydatną cechą aplikacji byłaby porównywarka taka, żeby wybierać jak najniższą kaloryczność. Aby zapewnić, że każdy obiekt będzie posiadał możliwość porównania możemy zaimplementować interfejs Comparable.

Implementacja tego interfejsu pozwala na używanie metod automatycznego sortowania w tablicach lub kolekcjach przy użyciu metody sort. W ten sposób mamy pewność, że elementy zbioru zostaną posortowane wg. wartości, którą chcemy.

Jest on typem generycznym zatem nie trzeba wykonywać rzutowania na typ Object jak w starszych wersjach Javy.

Interfejs Comparable zdefiniowany jest jedną metodą.

Jednakże zawiera długi opis tego jak powinna być implementowana metoda compareTo(T 0). Jeżeli różnica kaloryczności jest równa 0, to wartości są równe. Jeśli liczba jest dodatnia, to pierwsza wartość kaloryczna obiektu a jest większa, a jeśli wynik jest ujemny, to druga wartość kalorii b jest większa.

Implementacja interfejsu Comparable umożliwia nam proste sortowanie kolekcji. Gdybyśmy chcieli umieścić kolejne napoje w tablicy możemy skorzystać z metody sort() dostępnej w klasie Arrays. Do przykładu wykorzystamy klasy z poprzedniego listingu.

Podobną operację można wykonać na listach tablicowych ArrayList.

Zwróćmy też uwagę na wykorzystanie zmiennej interfejsowej do przechowywania obiektu listy. List jest interfejsem, a ArrayList klasą go implementującą.

Interfejs Iterable

Przydatny interfejs, dzięki któremu możliwe jest użycie pętli typu „for each” zamiast tradycyjnej for. Znacząco ułatwia operacje na zbiorach. Interfejs List go implementuje, a więc każda klasa, która implementuje interfejs Iterable również może wykorzystywać pętle typu „for each”.

Wynikiem będzie wypisanie kolejnych kalorii kolejnych napojów z kolekcji.

Porównanie interfejsów i klas abstrakcyjnych

Podstawową różnicą między klasami a interfejsami jest to, że dziedziczenie odbywa się tylko po jednej klasie nadrzędnej, a jeśli chodzi o interfejsy to można implementować ich wiele. Aby to zrobić należy po prostu po słowie implements wymienić interfejsy oddzielając przecinkiem.

Metody domyślne

Od Javy w wersji 1.8 interfejsy posiadają dodatkowy atut w postaci metod domyślnych ang. default method), które pozwalają w pełni zaimplementować metodę wewnątrz interfejsu.

Nie należy jednak próbować w ten sposób omijać ograniczenia dziedziczenia jednokrotnego i traktować interfejsów jako klasy. W przypadku, gdy metoda domyślna z dwóch interfejsów będzie miała taką samą nazwę, to kompilator zwróci błąd.

Metoda domyślna jest przekazywana do klasy, która implementuje dany interfejs. Można ją przesłonić, ale należy pamiętać, że wtedy zawsze będzie używana metoda zdefiniowana w klasie.

Czy obiekt implementuje interfejs?

Podobnie jak w przypadku klas można sprawdzić czy dany obiekt jest klasy, która implementuje dany interfejs operatorem instaceof.

Interfejsy podobnie jak i klasy abstrakcyjne nie pozwalają na tworzenie obiektów, jednak możliwe jest deklarowanie zmiennych, do których można przypisać obiekt klasy, która implementuje dany interfejs.

Interfejsy podsumowanie

Postaramy się teraz uporządkować wiedzę na temat interfejsów i wylistujemy, które czynności są dozwolone, a które zabronione.

  • Klasy mogą być implementować wiele interfejsów.
  • Interfejsy mogą implementować inne interfejsy.
  • Interfejsy mogą posiadać definicję metod, jednak muszą być opatrzone słowem kluczowym default.
  • Wszystkie metody w interfejsach są publiczne, ale w przypadku implementacji w klasie należy pamiętać, aby napisać odpowiedni modyfikator dostępu.
  • Interfejsy mogą zawierać stałe.
  • Nie mogą zawierać pól obiektowych ani typów prostych.
  • Można tworzyć zmienne interfejsowe i przypisywać do nich obiekty, które implementują dany interfejs.
  • Możliwe jest wykonanie testu czy obiekt jest klasy, która implementuje dany interfejs przy pomocy operatora instanceOf.

W następnej części pokażemy kolekcje i typy generyczne.