Jedną z dużych zmian, jakie przyniosła ze sobą Java 8, było dopuszczenie implementacji metod w interfejsach. Używając słowa kluczowego default, możemy zdefiniować metodę, która będzie mieć implementację:

Listing 1. Przykładowy interfejs z implementacją z Javy 8

interface SomeService{

    default void validate(Client client){
        Preconditions.checkNotNull(client);
    }

    default void someLogic(Client client){
        validate(client);
        TransformedClient transformedClient = transform(client);
        emit(transformedClient);
    }

    default void emit(TransformedClient transformedClient){
        System.out.println(transformedClient);
    }

    TransformedClient transform(Client client);
}

Oczywistym zastosowaniem jest przygotowanie metod szablonowych. Nasz interfejs realizuje pewną większą logikę, która wymaga byśmy w ramach implementacji, dostarczyli jedynie fragmentu logiki reprezentowanego przez metodę someLogic. Tyle tylko, że zarówno validate jak i emit nie powinny być zmieniane (takie założenie). Na poziomie interfejsu jedynym rozwiązaniem, które nam to może zagwarantować, jest zamiana tych metod na statyczne. Nie możemy utworzyć metod finalnych w interfejsach. Jednak nadal pozostaje problem ujawnienia części implementacji. Klienta nie powinno interesować, w jaki sposób przeprowadzana jest walidacja ani jak są emitowane zdarzenia. Z doświadczenia wynika, że te dwie metody prędzej czy później staną się rozwiązaniem zastępczym dla jakiegoś kawałka kodu, gdzieś w odległym miejscu systemu. Skoro dostarczają tych mechanizmów, to czemu by z nich nie korzystać, zamiast zrobić to porządnie, na przykład wydzielając odpowiednią klasę.

A jak w Javie 9?

Java 9 dostarcza nam znacznie lepszy mechanizm, który pozwala na rozwiązanie tego problemu:

Listing 2. Przykładowy interfejs z prywatnymi metodami z Javy 9

interface SomeService{

    private void validate(Client client){
        Preconditions.checkNotNull(client);
    }

    default void someLogic(Client client){
        validate(client);
        TransformedClient transformedClient = transform(client);
        emit(transformedClient);
    }

    private void emit(TransformedClient transformedClient){
        System.out.println(transformedClient);
    }

    TransformedClient transform(Client client);
}

I to wszystko. Otrzymujemy zgrabny kod, który ma ukryte, to co powinno być ukryte i ujawnia tylko, to co trzeba.

Podsumowanie

Dziedziczenie wielobazowe zwane też wielodziedziczeniem w Javie jest realizowane za pomocą interfejsów. Dzięki temu nie mamy problemów związanych z konfliktami na poziomie implementacji. Wprowadzenie metod z domyślną implementacją w Javie 8 wymogło wprowadzenie kilku reguł dotyczących nadpisywania metod i rozwiązywania konfliktów w przypadku takich samych sygnatur. W dużym skrócie opierają się one, na wymuszeniu na programiście jasnej deklaracji co chce zrobić.
Java 9 wprowadza do tego mechanizmu rozwiązania, które pozwalają na uniknięcie części konfliktów, poprzez ukrycie niektórych metod. Ważniejsze jest jednak uzyskanie możliwości ukrywania elementów interfejsu. Dzięki temu zachowana będzie hermetyzacja naszego rozwiązania na odpowiednim poziomie. Zagrożeniem może okazać się wykorzystywanie interfejsów zamiast klas abstrakcyjnych. Szczególnie tam, gdzie klasy abstrakcyjne nie miały właściwości (pól). W ten sposób powstaną potworki, które będą miały wiele niepowiązanych odpowiedzialności.