Appa Notka.
Uwaga. Od 16/01/2022 ten rozdział kursu zostaje udostępniony za darmo!
Znajdziesz tutaj teorię oraz ponad 10 opisanych przykładów użycia klasy Optional. Miłej lektury!
W Javie 8 pojawia się klasa, która umożliwia opakowanie wartości niezależnie od tego, czy ta wartość faktycznie istnieje, czy też jest nullem.
To bardzo praktyczne rozwiązanie pozwala na uniknięcie wszechobecnego przed Javą 8 sprawdzania, czy wartość jest różna od
null, by
następnie wykonać na niej jakąś operację.
Za każdym razem, gdy spodziewaliśmy się braku wartości, byliśmy zobowiązani do stworzenia warunku,
jak na poniższym przykładzie:
Na szczęście ktoś w końcu wpadł na pomysł, że brak wartości jest informacją cenną samą w sobie
i należy udostępnić rozwiązanie wspierające obsługę takiej sytuacji. Tak oto w Javie 8 pojawiła się klasa
Optional,
która opakowuje wartość, z jaką pracujemy w danym miejscu w kodzie. Dopuszcza się, że wartość ta będzie dostępna w obiekcie klasy
Optional
lub nie.
Po zajrzeniu do wnętrza klasy można zauważyć, że zawiera ona pole do przechowywania wartości, jak również kilka metod
operujących na tym polu. Jedną z metod jest metoda
isPresent, która sprawdza, czy wartość istnieje.
Wykonuje ona za nas dokładnie to samo sprawdzenie, które przez lata znajdowaliśmy w kodzie każdego programu Java,
a więc weryfikację
czy wartość jest różna od null:
Kolejna metoda -
get - odpowiada za pobranie wartości. Wykonujemy ją po
wcześniejszym sprawdzeniu, czy wartość jest dostępna,
gdyż jeśli wartość nie będzie istniała, a my uruchomimy metodę
get,
wówczas otrzymamy wyjątek
NoSuchElementException.
Tworzenie obiektu klasy Optional
Analizując kod klasy, napotkamy tam także metody do tworzenia obiektu typu
Optional.
Istnieją dwie takie metody, obie są statyczne. Jedna -
of - zakłada, że nasza wartość nie będzie nullem, a jeśli przez pomyłkę
prześlemy tam
null, wówczas zostanie on odrzucony przez metodę walidacyjną
requireNonNull z klasy
Objects.
W przeciwnym wypadku zostanie utworzony nowy obiekt klasy
Optional z ustawioną wartością.
Druga metoda -
ofNullable - pozwala na przekazanie do niej
null, ale wtedy zostanie
zwrócony obiekt klasy Optional,
który jest pusty i jest zdefiniowany za pomocą stałej o nazwie
EMPTY. Oczywiście, jeśli do metody
ofNullable
zamiast
null zostanie przekazany istniejąca wartość, wtedy zostanie utworzony nowy obiekt klasy
Optional z tą ustawioną wartością:
Klasa
Optional jest tak zaprojektowana, by zablokować programiście możliwość utworzenia instancji tej klasy bez użycia
wspomnianych metod statycznych
of i
ofNullable. Zostało to osiągnięte przez
ukrycie
widoczności konstruktorów poza ciałem klasy. Dodatkowo sama klasa oznaczona jest modyfikatorem
final:
Optional - Tworzenie metodą of
Po tym sporym wstępie przyszedł czas na pierwszy przykład. Zademonstrujemy tworzenie obiektu typu
Optional
oraz sprawdzenie, czy obiekt ten zachowuje się zgodnie z oczekiwaniami.
Najpierw tworzymy obiekt klasy
Optional w zalecany sposób, a więc używając metody
of
z podaną wartością. W tym przypadku jest to obiekt typu
String. Sprawdzamy, czy wartość istnieje, a następnie
pobieramy ją z obiektu opakowującego.
W kolejnej próbie wykorzystujemy metodę
of w niepoprawny sposób, ponieważ przekazujemy do niej
null. Powoduje to wyrzucenie wyjątku
NullPointerException:
Optional - Tworzenie metodą ofNullable
Drugi sposób tworzenia obiektu typu
Optional polega na użyciu metody
ofNullable.
Wynik wykonania kodu:
Pierwsza część zadziała podobnie jak wcześniej. Na wydruku pojawi się pobrana wartość. Natomiast stworzenie obiektu
z nullem zachowa się inaczej niż w przypadku metody
of. Tym razem dozwolone jest przekazanie nulla do metody, a
sprawdzenie warunku za pomocą metody
isPresent zwróci
false.
W związku z tym nie dojdzie do pobrania wartości, a jedynie zwrócony zostanie tekst zdefiniowany przez nas w ramach klauzuli
else.
Wykonanie całego kodu będzie miało wynik jak na obrazku.
Appa Notka. Klasa Optional zawiera jeszcze kilka innych metod,
które są bardzo istotne, na przykład orElse, orElseGet, orElseThrow.
Będziemy je omawiać w dalszej części tego rozdziału, już na kodzie konkretnych przykładów bazujących na strumieniach.
Optional - w strumieniu
No dobrze, wiesz już, jak wygląda
Optional i poznałe(a)ś dwa proste przykłady jego użycia.
Czas zaprezentować jak ta doskonale klasa ta współpracuje ze strumieniami w Javie. Na początek przykład na prostych obiektach.
Uruchamiamy
metodę klasy
Stream, która znajduje pierwszy element w strumieniu.
Jeśli taki element istnieje, wtedy metoda zwróci pierwszą wartość, opakowaną w obiekt klasy
Optional.
Pozostaje nam sprawdzić, czy wartość jest faktycznie dostępna, a jeśli tak, pobrać ją z optionala za pomocą metody
get.
Wynik wykonania kodu:
Optional - jeśli wartość nie istnieje
W poprzednim przykładzie wartość w optionalu istniała, ponieważ metoda
findAny
znalazła ją w strumieniu. W przypadku gdy w strumieniu nie ma żadnej wartości, wówczas możemy na przykład
rzucić wyjątek w bloku
else klazuzuli
if - else:
Wynik wykonania kodu:
Optional - orElseThrow
Zamiast samemu obsługiwać wyrzucenie wyjątku, możemy zatrudnić do tego zadania kolejną metodę klasy
Optional
o nazwie
orElseThrow. Metoda ta przyjmuje parametr w postaci wyrażenia lambda, a dokładniej implementacji interfejsu funkcyjnego
Supplier.
Zwracamy tam wyjątek, który powinien zostać wyrzucony, jeśli wartość w optionalu nie zostanie znaleziona.
Wynik wykonania kodu:
Optional - orElse
W przypadku gdy optional nie zawiera wartości, możemy też zwrócić wartość awaryjną (domyślną).
Używamy wtedy kolejnej metody z klasy
Optional, metody
orElse:
Wynik wykonania kodu:
Optional - orElseGet
Istnieje jeszcze inna możliwość obsługi alternatywnej ścieżki.
Przydaje się, gdy chcemy wykonać algorytm dostarczający pewne rozwiązanie,
jeśli wartość w optionalu nie istnieje. Wtedy możemy zdefiniować obiekt interfejsu
Supplier i przekazać
go do metody
orElseGet:
Wynik wykonania kodu:
Optional - w metodzie filter
W rozdziale
Strumienie - Filtry
przygotowaliśmy kilka przykładów na działanie filtrów w strumieniach. Przy okazji omawiania optionali, warto jest
dodać do tego jeszcze jeden przykład. Najpierw stworzymy klasę
MovieItem,
po czym zbudujemy kod zasadniczej części programu:
Poniżej przetwarzany dane ze strumienia z filmami. Naszym celem jest wykonanie dwóch przeciwstawnych operacji.
Najpierw
przepuszczamy dalej tylko te obiekty, które mają ustawioną nazwę gatunku, a następnie
wybieramy tylko te,
którym tej nazwy brakuje (
null został przekazany do nich przez konstruktor). W drugim przypadku zaprzeczamy warunek za pomocą znaku
wykrzyknika:
Wynik wykonania kodu:
Optional - filtracja z referencją do metody isPresent
Klasa
MovieItem do bieżącego przykładu wygląda dokładnie tak samo, jak poprzednio.
Teraz jednak wykonamy trochę inny algorytm. Naszym celem będzie udokumentowanie użycia referencji do metody
isPresent
z klasy
Optional. Zresztą w ogóle użyjemy samych referencji do metod.
Najpierw
mapujemy obiekt
klasy MovieItem na obiekt typu Optional (poprzez wyciągnięcie go z obiektu filmu).
Następnie
naszą tytułową metodą sprawdzamy, czy istnieje wartość w optionalu (filtrujemy), po czym
wywołujemy metodę get (znowu mapujemy),
by tę wartość wyciągnąć.
Na tym etapie mamy już wszystkie wartości, ale niektóre z nich będą się duplikować ("Dramat" i "Komedia" są określone dla dwóch filmów).
Potrzebujemy pozbyć się duplikatów, więc uruchamiamy metodę
distinct na strumieniu.
Tak określony strumień ze zmapowanymi, odfiltrowanymi i niepowtarzającymi się wartościami poddajemy działaniu metody
forEach,
która drukuje pozostałe na tym etapie wartości.
Wynik wykonania kodu:
Optional - zaprzeczenie referencji do metody isPresent
Z referencją do metody
isPresent i ogólnie z referencjami do metod logicznych wiąże się jeden problem.
Nie da się ich bezpośrednio zaprzeczyć poprzez użycie znaku wykrzyknika. Coś takiego się nam nie skompiluje:
Zatem, co można zrobić w takiej sytuacji? Wtedy możemy skonstruować
metodę w testowanym obiekcie, która zwróci PRAWDĘ, jeśli
wartość nie jest dostępna w optionalu. W ciele metody korzystamy z
zaprzeczenia metody
isPresent,
co przełoży się na potwierdzenie braku wartości:
Teraz wystarczy już tylko użyć referencji do metody
isEmptyGenre i nie musimy się więcej martwić o zaprzeczanie
dostępności gatunku. Sumarycznie, patrząc na zmiany w obu klasach, musieliśmy napisać trochę dodatkowego kodu, ale za to dostajemy rozwiązanie wielokrotnego użytku.
Wynik wykonania kodu:
Optional - filtracja z ifPresent
W klasie
Optional istnieje jeszcze metoda, która pozwala od razu na wykonanie określonego zadania,
jeśli tylko wartość jest dostępna. Metoda ta nazywa się
ifPresent i przyjmuje parametr w postaci
implementacji interfejsu funkcyjnego
Consumer. Przykładem takiej implementacji jest referencja do metody
println w poniższym przykładzie:
Wynik wykonania kodu:
Pełne zrozumienie działania klasy
Optional jest szalenie ważne. Wynika to z tego, że
regularnie, w różnych miejscach kodu, jesteśmy zmuszeni do podejmowania decyzji warunkowych oraz filtracji danych.
Pewnym wyzwaniem może być przyzwyczajenie się do rezygnacji ze sprawdzenia, czy dana wartość jest różna od
null,
które przez lata przewijało się w naszych programach. W szerszej perspektywie jest to jednak całkiem przyjemna zmiana.
Autor: Jarek Klimas
Data: 03 stycznia 2024
Labele: Backend, Podstawowy, Java
Czy informacje, które otrzymałeś, były pomocne?
Jeśli tak, zapraszam Cię do podarowania mi kawy.
Masz pytanie dotyczące prezentowanego materiału?
Coś jest dla Ciebie niejasne i Twoje wątpliwości przeszkadzają Ci w pełnym zrozumieniu treści?
Napisz do nas maila, a my chętnie znajdziemy odpowiednie rozwiązanie.
Najciekawsze pytania wraz z odpowiedziami będziemy publikować pod rozdziałem.
Nie czekaj. Naucz się programować jeszcze lepiej.