Java 8 przyniosła wiele nowości. Według wielu, po wersji 5 języka, była to najbardziej rewolucyjna aktualizacja. I choć od wprowadzenia tejże minęło już kilka ładnych lat – wciąż wiele osób gubi się próbując zapanować nad niektórymi zmianami. Dziś na tapet wezmę zmiany w interfejsach, które pojawiły się wraz z aktualizacją Javy z 2014 roku.
Od Javy w wersji 8 można w interfejsach nie tylko deklarować, ale i definiować metody. Umożliwia to tworzenie metod, które nie muszą być później implementowane w klasach wykorzystujących dany interfejs. Oprócz tego w interfejsach można umieszczać metody statyczne, które we wspomnianych klasach implementowane być nie mogą.
Nasuwa to jednoznaczne skojarzenia z klasami abstrakcyjnymi. Otóż dotychczas klasa w pełni abstrakcyjna (tj. będąca abstrakcyjną oraz mająca wszystkie metody abstrakcyjne) pełniła dokładnie taką samą funkcję jak interfejs. W klasie abstrakcyjnej mogły być zwykłe metody, czyli takie, które można było przesłonić w klasie pochodnej (jednakże nie było to konieczne), oraz metody finalne, których nadpisywać nie można było. Wnioski o tym, że interfejsy i klasy abstrakcyjne bardzo się do siebie zbliżają nasuwają się same.
Stwórzmy zatem interfejs z metodą, nawijmy to, “klasyczną”, statyczną i domyślną, a następnie zaimplementujmy go w utworzonej przez nas klasie.
package it.gniado; public interface Vehicle { int getAmountOfWheels(); default double getEngineCapacity(){ return 2.0; } static String getSound(){ return "pyr pyr"; } }
package it.gniado; public class SmallMotorcycle implements Vehicle{ @Override public int getAmountOfWheels(){ return 2; } @Override public double getEngineCapacity(){ return 0.125; } }
package it.gniado; public class Car implements Vehicle { @Override public int getAmountOfWheels(){ return 4; } }
Mamy tutaj interfejs Vehicle i dwie proste klasy go implementujące: SmallMotorcycle oraz Car. Wykorzystanie ich metod w przykładowej klasie Main jest trywialne i przewidywalne:
package it.gniado; public class Main { public static void main(String[] args) { Vehicle smallMoto = new SmallMotorcycle(); Vehicle car = new Car(); System.out.println(car.getAmountOfWheels()); System.out.println(smallMoto.getAmountOfWheels()); System.out.println(car.getEngineCapacity()); System.out.println(smallMoto.getEngineCapacity()); System.out.println(Vehicle.getSound()); } }
Taka klasa jak powyżej daje spodziewane wyniki. Na konsoli wydrukują się kolejno liczby kół: 4 i 2, następnie pojemności: 2.0 oraz 0.125 a na koniec dźwięk silnika, czyli “pyr pyr pyr”.
Jednakże silniki samochodowe i motocyklowe mają diametralnie odmienny dźwięk. Sensownym zatem byłoby nadpisać metodę getSound w klasie SmallMotorcycle tak, aby zwracała dźwięk “wrrrrrrrrrym!”. Spróbujmy.
package it.gniado; public class SmallMotorcycle implements Vehicle{ @Override public int getAmountOfWheels(){ return 2; } @Override public double getEngineCapacity(){ return 0.125; } @Override public String getSound(){ return "wrrrrrrrrrym!"; } }
Podczas kompilacji mamy błąd:
Error:(16, 5) java: method does not override or implement a method from a supertype
Usunięcie adnotacji @Override na niewiele się zda, bo wtedy próba wykorzystania tej metody daje nam kolejny błąd:
package it.gniado; public class Main { public static void main(String[] args) { Vehicle smallMoto = new SmallMotorcycle(); Vehicle car = new Car(); System.out.println(car.getAmountOfWheels()); System.out.println(smallMoto.getAmountOfWheels()); System.out.println(car.getEngineCapacity()); System.out.println(smallMoto.getEngineCapacity()); System.out.println(Vehicle.getSound()); System.out.println(smallMoto.getSound()); } }
I podczas kompilacji:
Error:(17, 46) java: illegal static interface method call the receiver expression should be replaced with the type qualifier 'it.gniado.Vehicle'
Dopiero zmiana typu zmiennej smallMoto z Vehicle na SmallMotorcycle pozwala skorzystać z pseudoprzesłoniętej metody getSound(). Eliminuje to jednak cały zysk z polimorfizmu, jaki możemy uzyskać wykorzystując interfejs Vehicle.
package it.gniado; public class Main { public static void main(String[] args) { SmallMotorcycle smallMoto = new SmallMotorcycle(); Vehicle car = new Car(); System.out.println(car.getAmountOfWheels()); System.out.println(smallMoto.getAmountOfWheels()); System.out.println(car.getEngineCapacity()); System.out.println(smallMoto.getEngineCapacity()); System.out.println(Vehicle.getSound()); System.out.println(smallMoto.getSound()); } }
4 2 2.0 0.125 pyr pyr wrrrrrrrrrym! Process finished with exit code 0
Wygląda na to, że interfejsy i klasy abstrakcyjne coraz bardziej się do siebie zbliżają, a jedyną rzeczą odróżniającą je w tej chwili od siebie jest wielodziedziczenie, które można za pomocą interfejsu jako-tako osiągnąć (a co odradzają twórcy Javy). Patrząc jednak na słowo kluczowe var w 10 wersji języka można spodziewać się likwidacji interfejsów lub klas abstrakcyjnych w przyszłości (lub obu tych struktur na rzecz czegoś kompletnie nowego).