Zmiany w interfejsach dzięki Javie 8

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

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *