Klasa Optional została wprowadzona w Javie 8. Pozwala wygodnie obsługiwać potencjalne NullPointerException. O jej podstawowych możliwościach przeczytasz np. tutaj: link.
Dlaczego używać Optionali?
Używając Optionala mamy trochę więcej kodu. Ale mamy duży uzysk. Ludzie (korzystający z funkcji, która zwraca takiego Optionala) będą wiedzieć, że ten obiekt może być lub nie i muszą sobie z tym jakoś poradzić i obsłużyć. Nikt nie natknie się na niespodziewanego NullPointerExceptiona.
Wyrażenia lambda w Optionalach
Załóżmy, że mamy taki kod, używający Optionala:
![]()
![]()
![]()
Możemy go uprościć:
![]()
![]()
![]()
Metoda ifPresent() przyjmuje jeden argument. Interfejs funkcyjny (taki, który posiada tylko jedną metodę abstrakcyjną) Consumer.
Reprezentuje operację, która przyjmuje jeden argument, czyli w tym przypadku obiekt opcjonalny i nic nie zwraca.?
Metoda w Consumer to accept(). Przyjmuje jeden argument. Nie zwraca nic. Jeżeli Optional nie zawiera obiektu, to nic się nie dzieje.
Metody znane ze strumieni
Korzystając z Optionali mamy do dyspozycji metody znane ze stream API. Np. .map() oraz .filter(). Zwracają Optional pożądanego typu.
Poniższy kod mapuje nasz Optional<Product> na Optional<String>, czyli na nazwę produktu. Następnie otrzymaną nazwę ponownie mapujemy. Tak, aby wypisać ją wielkimi literami.
![]()
![]()
![]()
Metodę .fliter() stosujemy, gdy chcemy aby obiekt w Optionalu spełniał konkretny warunek. W poniższym przykładzie filtrujemy produkty o wyższej cenie niż 100 zł.
![]()
![]()
![]()
Jako argument funkcja .filter() przyjmuje interfejs Predicate, który zwraca nam true lub false.
Do dyspozycji mamy również metodę .flatMap(). Tak jak .map() przekształca wartość. Różnica polega na tym, że .flatMap() na wejściu przyjmuje Optionala i rozpakowuje go. Następnie przekształca i opakowuje znowu. Metoda .map() przekształca wartości tylko wtedy, gdy są rozpakowane.
Przykład:
![]()
![]()
![]()
Korzystając z .map() metoda getName zwraca nam Optionala. Nie String. Dodatkowo samo mapowanie opakowuje nasz obiekt w Optional. W ten sposób otrzymujemy zagnieżdżony obiekt Optional.
Dlatego musimy dodać dodatkowe wywołanie. W ten sposób pozbywamy się opakowania w Optional. W metodzie .flatMap() operacja ta wykonuje się niejawnie. Kod jest prostszy.
Pusty Optional
Do naszego łańcucha wywołań dorzucimy obsługę pustych Optionali. Mamy do dyspozycji trzy metody: .orElse(), .orElseGet() i .orElseThrow(). Ostatnia rzuca wyjątkiem.
![]()
![]()
![]()
Dwie pierwsze działają podobnie. Istnieje subtelna różnica. Metoda .orElse() jako argument przyjmuje zwykły obiekt. Metoda .orElseGet() przyjmuje obiekt typu Supplier.
Jeżeli mamy niepusty Optional, to .orElseGet() nie wywoła się w ogóle. Natomiast .orElse() wywoła się zawsze, natomiast nie zostanie wykorzystana.
Przykładowo mamy taki kod:
![]()
![]()
![]()
Obiekt Optional<Foo> zawiera w sobie obiekt. Dlatego w pierwszym przypadku nowy obiekt Foo zostanie utworzony (to co jest w orElse wykona się), ale nie wykorzystany.
W drugim obiekt Foo nie zostanie utworzony w ogóle. Zostanie utworzony, dopiero gdy nie będzie obiektu Foo w środku. Jest to istotne jeśli chodzi o wydajność. Dodatkowe wywołanie (utworzenie obiektu) = dodatkowy koszt w zasobach.
Minusem stosowania tych metod jest to, że nie możemy stworzyć np. konstrukcji typu .map().ifPresent().orElseThrow(). I wtedy, gdy chcemy uzyskać nazwę produktu musimy utworzyć zmienną typu String. A następnie dopiero pobrać z niej wartość:
![]()
![]()
![]()
Optionale w Javie 9
Java 9 wprowadziła dodatkowe trzy metody, które możemy stosować w Optionalach: .stream(), .or() oraz .ifPresentOrElse().
Pierwsza z nich zmienia naszego Optionala w strumień. Jeżeli jest pusty, to zwraca pusty strumień. Strumienie pozwalają opakować dane. Następnie możemy je wysyłać do innych systemów lub przetwarzać.
Kolejna .or() pozwala nam przesłać wartość domyślną. Jest ona ustawiana, gdy w którymś momencie łańcucha wywołań dostaniemy pustego Optionala. Np.:
![]()
![]()
![]()
Jeżeli produkt o nazwie “Table” się nie znajdzie, to ustawiamy domyślny produkt o nazwie “Chair”. Na końcu łańcucha możemy skorzystać z ifPresent.
Z kolei .ifPresentOrElse() wykona akcję, jeśli będzie istniał obiekt albo wykona drugą akcję, gdy obiekt nie będzie istniał.
Przyjmuje implementację interfejsów Consumer i Runnable. Pierwszy działa jak metoda .ifPresent(). Drugi to interfejs funkcyjny z metodą .run(). Nie przyjmuje żadnych argumentów. Nic nie zwraca. Uruchamia się, gdy Optional jest pusty. W poniższym przykładzie wyświetla napis:
![]()
![]()
![]()
Nie przekazuj Optionala jako parametru
Wykorzystanie Optionala jako argument metody może być kuszące. Nie jest to jednak dobry pomysł. Przykładowo mamy taki kod:
![]()
![]()
![]()
Dwa argumenty to zawsze Optional. Wiążą się z tym problemy. Po pierwsze mamy przypadek, gdy wartości te są stałe i dobrze znane. Ale musimy opakować je w Optional, żeby móc wywołać metodę sendProduct().
Po drugie metoda sendProduct() musi obsłużyć cztery potencjalne zachowania. W ten sposób łamie zasadę pojedynczej odpowiedzialności.
Po trzecie nie mamy informacji jak metoda ta obsługuje nasz Optional. Może zmieniać logikę biznesową. Może też rzucać losowy wyjątek. Nie wiemy.
Jak rozwiązać te problemy?
Gdy mamy wartości opakowane w Optional.
Po pierwsze możemy wykorzystać metodę orElse(). W ten sposób dostarczymy wartość domyślną. Gdy Optional jest pusty.
![]()
![]()
![]()
Metoda zawsze ma dostarczone wszystkie argumenty. Nie musi obsługiwać wartości pustych.
Innym rozwiązaniem jest przeciążanie metod. Możemy to zastosować, gdy do funkcji sendProduct() nie zawsze trzeba dostarczyć wszystkie argumenty.
W ten sposób dzielimy metodę sendProduct(). Jeżeli wystąpi pusty Optional to jej odpowiednia wersja zostanie wywołana.
Jeśli podobał Ci się artykuł i chcesz więcej już teraz to koniecznie sprawdź TUTAJ.
Źródła:
https://bulldogjob.pl/news/1579-sposoby-na-unikniecie-problemow-z-java-optional
http://devfoundry.pl/optional-java-pl/
https://www.baeldung.com/java-optional



