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).