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.
1 2 3 4 |
public interface NazwaInterfejsu { int metoda(int a); } |
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.
1 2 3 4 5 6 7 8 9 10 |
public interface NazwaInterfejsu { default void metoda(int a){ System.out.print(getInt()); } private int getInt(){ return 0; } } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public abstract class Zywnosc implements Comparable{ private int kalorie; private String nazwa; Zywnosc(int kcal, String naz){ kalorie = kcal; nazwa = naz; } public int compareTo(Zywnosc other){ return (this.kalorie - other.getKalorie()); } public int getKalorie(){ return kalorie; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Napoj extends Zywnosc { private float pojemnosc; Napoj(int kcal,String nazwa){ super(kcal, nazwa); } public static void main(String[] args){ Zywnosc a = new Napoj(500, "Cola"); Zywnosc b = new Napoj(400, "Tonic"); int n = a.compareTo(b); if(n == 0){ System.out.println("Obiekty posiadają równą kaloryczność"); } else if(n > 0){ System.out.println("Pierwszy obiekt jest bardziej kaloryczny"); } else{ System.out.println("Drugi obiekt jest bardziej kaloryczny"); } } |
Interfejs Comparable
zdefiniowany jest jedną metodą.
1 |
public int compareTo(T o); |
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.
1 2 3 |
Zywnosc[] z = {a,b}; Arrays.sort(z); //kolejność b, a |
Podobną operację można wykonać na listach tablicowych ArrayList
.
1 2 3 4 5 6 |
List napoje = new ArrayList<>(); napoje.add(new Napoj(500, "Cola")); napoje.add(new Napoj(400, "Tonic")); System.out.println(napoje.get(0).getKalorie()); //500 napoje.sort(null); System.out.println(napoje.get(0).getKalorie()); //400 |
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”.
1 2 3 4 5 6 7 8 |
List napoje = new ArrayList<>(); napoje.add(new Napoj(500, "Cola")); napoje.add(new Napoj(400, "Tonic")); napoje.sort(null); for(Zywnosc nap: napoje) { System.out.println(nap.getKalorie()); } |
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.
1 |
class Zywnosc implements Interfejs1, Interfejs2 |
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
.
1 2 3 |
Napoj herbata = new Napoj(); if(herbata instanceof Comparable) ... |
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.