Interfejsy w JDK 7
Zanim zacznę opisywać zmiany jakie zaszły w interfejsach w Javie w wersji ósmej, takie jak np. metody domyślne czy metody statyczne, spójrz na moje małe podsumowanie, jak działały one w poprzedniej wersji.
Właściwości interfejsów w JDK 7:
- interfejs implementujesz w klasie a nie dziedziczysz (jak w przypadku klas abstrakcyjnych)
- interfejs może być rozszerzony o inne interfejsy (więcej niż jeden, w przeciwieństwie do klas, które mogą dziedziczyć tylko po jednej klasie)
- jedna klasa może implementować dowolnie wiele interfejsów
- domyślnie dostęp do składowych interfejsu jest publiczny
- interfejs może przetrzymywać stałe
- głównym celem interfejsu jest deklaracja metod (składowanie ich sygnatur). Metody te niejawnie są abstrakcyjne (czyli bez ciała).
Metody statyczne
Nie ma tu wielkiej filozofii. Po prostu od teraz możesz pisać publiczne metody statyczne w swoim interfejsie. Nie łamie to idei interfejsu (czyli bezstanowości), ponieważ metoda statyczna należy do klasy (w tym przypadku interfejsu) a nie do obiektu (instancji).
Przykładowy interfejs zawierający stałą, jedną sygnaturę i metodę statyczną.
public interface Flying { public static final String NAME = "Flying interface"; public void fly(); public static boolean isAllowToFly(String weather) { return "good".equals(weather) ? true : false; } }
Metody domyślnie
Kolejną nowością w Javie 8 są metody domyślne. Przypominają one powyższe metody statyczne, tzn. po zaimplementowaniu interfejsu w swojej klasie, możesz odwoływać się do tej metody. Ważna różnica pomiędzy nimi, jest to, że metody domyślne tak jak każde metody instancyjne (niestatyczne) można nadpisywać. Najprościej metody domyślne można rozumieć jako podstawową implementację danej sygnatury, zawartej już w samym interfejsie.
public interface Flying { public static final String NAME = "Flying interface"; public void fly(); public static boolean isAllowToFly(String weather) { return "good".equals(weather) ? true : false; } default void showName(){ System.out.println(NAME); } }
Przykład klasy, która nadpisuje domyślną metodę showname.
public class Plane implements Flying{ @Override public void fly() { System.out.println("fly.."); } @Override public void showName(){ System.out.println("Show plane"); } }
Pewnym problemem wynikającym z posiadania metod domyślnych może być posiadanie konfliktów przy używania tych samych nazw w różnych interfejsach.
public interface Landing { public static final String NAME = "Landing interface"; public void land(); default void showName(){ System.out.println(NAME); } }
Mój nowy interfejs posiada tą samą metodę domyślną jak interfejs Flying. Co się stanie w przypadku implementacji obu interfejsów w tej samej klasie? W takim przypadku kompilator zgłosi błąd:
Exception in thread "main" java.lang.Error: Unresolved compilation problem: Duplicate default methods named showName with the parameters () and () are inherited from the types Landing and Flying
Tego typu problem nazywa się problemem diamentu (ang. diamond problem*). Aby go obejść można nadpisać skonfliktowaną metodę i w jej ciele wybrać, którą implementację wybierasz.
public class PlaneConflict implements Flying, Landing{ @Override public void land() {} @Override public void fly() {} @Override public void showName() { Flying.super.showName(); } }
Interfejsy funkcyjne
Metody domyślne i statyczne to nie jedyne zmiany jakie zaszły w interfejsach. Ostatnią ciekawą kwestią dodaną do JDK 8 jest możliwość tworzenia interfejsów funkcyjnych, ale ten temat omówię już w kolejnej lekcji.
* https://en.wikipedia.org/wiki/Multiple_inheritance
Kod do lekcji: https://github.com/developeronthego/java-jdk8/tree/master/src/main/java/java8/interfaces
Przypomnienie jak działają interfejsy: Java #27: implementacja interfejsu