Upgrade to Java8 - wyrażenia lambda i interfejsy

Postanowiłem troszkę pobawić się w praktyce z już dawno powstałą Javą 8 oraz dowiedzieć się o niej więcej. Post będzie dotyczył wyrażeń lambda oraz interfejsów.

Co to jest lambda?

Jest to nic innego jak łatwiejsza implementacja klas anonimowych. Przedstawię to na przykładzie:

colors.sort((o1, o2) -> o1.getPosition() - o2.getPosition());

colors.sort(new Comparator<Figure>() { @Override public int compare(Figure o1, Figure o2) { return o1.getPosition() - o2.getPosition(); } });

Obydwa powyższe zapisy robią to samo. A więc wyrażenia lambda wprowadzone (dość późno patrząc na konkurencyjny C#) tak na prawdę nie wprowadza nowych możliwość.

A więc po co lambda?

Uważam, że zapisy lambda są o wiele czytelniejsze niż implementacja klasy anonimowej. Wraz z przeczytaniem moich wpisów o czystym kodzie wiesz, że to bardzo ważne. Jednakże nie uważam tego jako kluczową zmianę, ponieważ w samym działaniu języka Java nic ona nie zmienia.

Z wyrażeniami lambda jest jeszcze jeden problem. Możemy je stosować tylko do interfejsów funkcyjnych.

Interfejs funkcyjny

Jest to interfejs, który posiada tylko jedną niedomyślną i niestatyczną metodę w deklaracji metod. I to właśnie dlatego tylko takie interfejsy można używać w wyrażaniach lambda. Skąd kompilator miałby wiedzieć, którą funkcję wybrać?

Powstała do nich specjalna, ale opcjonalna, adnotacja @FunctionalInterface.

Metody domyślne w interfejsach? Co jeszcze?

Tutaj znajdują się rewolucyjne zmiany. Powstały metody domyślne w interfejsach, czyli implementacje metod w interfejsach. Przypominać to może klasy abstrakcyjne. Według mnie tego typu implementacje właśnie tam powinny wylądować. Jednakże, po dłuższej analizie tematu poznałem dodatkowe możliwości tego zjawiska.

  • Wielodziedziczenie interfejsów pozwala na połączenie kilku implementacji różnych funkcji. Każdy programista w języku Java wie, że klas nie możemy dziedziczyć wielokrotnie. Tutaj jednak osobiście mam zastrzeżenia, że nie powinniśmy tego nadużywać, a wręcz bronić się od tego.
  • Interfejsy funkcyjne mogą mieć kilka funkcji, ale niektóre właśnie zaimplementowane. Wydaje mi się, że to była główna przyczyna powstania tego mechanizmu. Z brakiem możliwości rozwinięcia interfejsu tylko i wyłącznie z powodu, że używamy jego jako interfejs funkcyjny, moglibyśmy przestać pisać z wyrażeniami lambda lub tworzyć brzydkie struktury, aby tylko to obejść.
  • Pozwala to na nieużywanie copy-paste przy implementacji jakiejś standardowej metody. Również interfejs możemy uzyskać od osoby drugiej i wtedy naszym zadaniem może być zaimplementowanie tylko niedomyślnych metod. Powoduje to zmniejszenie ilości implementacji końcowego użytkownika.

Na szczęście nie ma co się martwić, metody obiektów są zawsze ważniejsze od metod domyślnych interfejsów.

Wygląd takiego interfejsu przedstawia się następująco:

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Jest to struktura interfejsu dodanego wraz z Javą 8. Większą ilość możecie znaleźć w paczce java.util.function.

Podsumowując

Java 8 wprowadziła dobrą zmianę w sprawie poprawienia czytelności kodu (lambda) oraz unikania copy-paste (metody domyślne). Zmianę tą można zaliczyć tylko na plus. Nie ma w niej żadnej wady.

Comments

Popular posts from this blog

Why TDD is bad practice?

How correctly imitate dependencies?

Software development using Angular 5 with Spring Boot