Optional orElse vs orElseGet
Na tę pułapkę często łapią się programiści Java, nawet Seniorzy! Poznaj różnicę między metodami orElse a orElseGet i korzystaj z Optionala świadomie

Po co nam Optional?
Przed Javą w wersji 8 mieliśmy tylko dwa sposoby na napisanie metody, która w pewnych okolicznościach nie mogła zwrócić żadnego obiektu:
- Zwrócić
null
- Wyrzucić wyjątek
Żadne z tych rozwiązań nie było perfekcyjne. Wyjątki powinny być zarezerwowane dla wyjątkowych sytuacji. Poza tym ich wyrzucanie jest kosztowne, ponieważ przechwytywany jest cały ślad stosu (ang. Stack trace) podczas tworzenia wyjątku. Z kolei zwracanie z metody null
powoduje, że klient (klasa wywołująca tę metodę) musi dodać sprawdzenie, czy zwrócony obiekt nie jest null
. Jeżeli tego nie zrobi, a metoda zwróci null
, zostanie wyrzucony wyjątek NullPointerException
przy próbie operacji na tym pustym obiekcie.
W Javie 8 został dodany trzeci sposób: klasa Optional<T>
, która reprezentuje niezmienny (ang. Immutable) kontener opakowujący referencję do obiektu T
lub nie opakowujący nic. Gdy Optional nic nie opakowuje, jest po prostu pusty (ang. Empty).
Zaczynając od Javy 8 metoda, która w pewnych okolicznościach nie może zwrócić żadnego obiektu powinna zwracać Optional<T>
. Wyjątkiem są tu kolekcje, mapy, streamy, tablice i inne typy kontenerowe. W takim wypadku lepiej po prostu zwrócić je puste niż opakowane Optionalem. Optional nie powinien także opakowywać innego Optionala.
Jak stworzyć Optionala?
Optional może zostać stworzony na trzy sposoby. Każdy z nich wymaga wywołania innej metody. Poniżej ich sygnatury:
public static <T> Optional<T> empty()
public static <T> Optional<T> of(T value)
public static <T> Optional<T> ofNullable(T value)
Wywołanie metody empty()
stworzy pustego Optionala, czyli nie opakowującego żadnego obiektu.
Wywołanie metody of(T value)
stworzy Optionala opakowującego obiekt T
. Metoda ta powstała z myślą przekazywania obiektu nie będącego null
. Jeżeli będzie, zostanie wyrzucony wyjątek NullPointerException
.
Wywołanie metody ofNullable(T value)
stworzy Optionala opakowującego obiekt T
, jeżeli obiekt ten nie jest null
. W przypadku, gdy obiekt T
będzie null
, zostanie stworzony pusty Optional. Jest to tak jakby kombinacja dwóch poprzenich metod.
Podsumowując:
empty()
używamy do stworzenia pustego Optionalaof(T value)
używamy gdy mamy pewność, że obiektT
nie będzienull
ofNullable(T value)
używamy w każdym innym wypadku. ObiektT
może byćnull
Przykład użycia:
Optional<String> emptyOptional = Optional.empty();
Optional<String> optionalOfValue = Optional.of("javowiec");
Optional<String> emptyOptionalOrOfValue = Optional.ofNullable(nullOrString);
Metoda get
Jednym ze sposobów zwrócenia obiektu opakowanego Optionalem jest wywołanie metody:
public T get()
String resultGet = Optional.of("javowiec").get();
Nie jest to jednak dobry sposób. Metody tej możemy użyć tylko wtedy, gdy jesteśmy całkowicie pewni, że w każdej sytuacji Optional będzie opakowywał obiekt. Jeżeli będzie pusty, wywołanie metody get
spowoduje wyrzucenie wyjątku NoSuchElementException
. Optional został stworzony z myślą pozbycia się wyjątku NullPointerException
, a nie po to by jeden wyjątek zastąpić drugim 😁
Metoda orElse
Lepszym sposobem jest użycie metody:
public T orElse(T other)
String resultOrElse = Optional.ofNullable(test).orElse("default");
Jeżeli Optional nie jest pusty, zwróci opakowywany obiekt. W przeciwnym wypadku zwróci parametr przekazany do metody orElse
. Pułapka, o której wspominałem na początku dotyczy właśnie tej metody i już za chwilę wyjaśnię o co chodzi.
Metoda orElseGet
Kolejnym dobrym sposobem jest użycie metody:
public T orElseGet(Supplier<? extends T> supplier)
String resultOrElseGet = Optional.ofNullable(test).orElseGet(Main::getDefault);
Metoda orElseGet
działa na podobnej zasadzie co orElse
. Jeżeli Optional nie jest pusty, zwróci opakowywany obiekt. W przeciwnym wypadku zwróci wynik wywołania instancji interfejsu funkcyjnego Supplier
przekazany do metody orElseGet
.
Interfejsy funkcyjne zostały dodane w Javie 8 podobnie jak Optionale. Interfejs funkcyjny Supplier
posiada jedną metodę bez parametrów: T get()
, która ma za zadanie po prostu zwrócić wynik. Instancje interfejsów funkcyjnych można stworzyć z wykorzystaniem wyrażeń lambda, referencji metod czy referencji konstruktorów:
() -> metoda() // Wyrażenie lambda
Klasa::metoda // Referencja metody
Klasa::new // Referencja konstruktora
Pułapka, czyli orElse vs orElseGet
Jak już pewnie zauważyłeś, orElse
oraz orElseGet
różnią się parametrem metody. Metoda orElse
przyjmuje jako parametr obiekt T
, natomiast metoda orElseGet
przyjmuje jako parametr interfejs funkcyjny Supplier
, a w zasadzie jego instancję.
Nieumiejętne rozróżnienie sytuacji, w których należy użyć jednej z tych metod może doprowadzić do spadku wydajności naszego kodu. Metodę orElse
używamy tylko wtedy, gdy posiadamy już stworzony obiekt. Jeżeli w parametrze zawrzemy metodę, która zwraca obiekt, metoda ta zostanie zawsze wykonana. Nawet, jeśli Optional nie jest pusty. Wyobraź sobie, że jeżeli Optional będzie pusty, będziemy chcieli wywołać metodę, która zwróci coś z Bazy Danych. Użycie w tym przypadku metody orElse
spowoduje, że zapytanie do Bazy Danych będzie zawsze wykonywane. Bez względu na to, czy Optional jest pusty czy nie!

W sytuacji, gdy Optional może być pusty i chcemy zwrócić obiekt, który jeszcze nie jest gotowy (np. trzeba go ściągnąć z Bazy Danych lub stworzyć), należy użyć metody orElseGet
. Metoda ta gwarantuje, że metoda interfejsu funkcyjnego Supplier
zostanie wywołana tylko wtedy, gdy Optional jest pusty.

Prostym programem można wykazać tę prawidłowość:
package pl.javowiec;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
// Zmienna testowa
String test = "javowiec";
System.out.println("Test variable: " + test);
// Przykład użycia Optional.orElse
System.out.println("[1] Optional.orElse example");
String resultOrElse = Optional.ofNullable(test).orElse(getDefault());
System.out.println("Optional.ofNullable(test).orElse(getDefault()) returns: " + resultOrElse);
// Przykład użycia Optional.orElseGet
System.out.println("[2] Optional.orElseGet example");
String resultOrElseGet = Optional.ofNullable(test).orElseGet(Main::getDefault);
System.out.println("Optional.ofNullable(test).orElseGet(Main::getDefault) returns: " + resultOrElseGet);
}
private static String getDefault() {
// Zwrócenie domyślnego napisu
System.out.println("Main.getDefault() method has been invoked");
return "default";
}
}
Co robi ten program? Ustawia zmienną testową, następnie opakowuje ją w dwóch Optionalach. Zwracamy obiekt opakowany Optionalami poprzez wywołanie metody orElse
oraz orElseGet
. Jeżeli zmienna testowa będzie null
, to zwrócimy domyślny napis "default"
metodą getDefault()
.
Po uruchomieniu tego programu zauważymy, że linia 14, czyli metoda orElse
zwraca opakowany napis "javowiec"
, ale wywołuje też metodę getDefault()
, pomimo że zmienna testowa nie jest null
. Metoda getDefault()
została wywołana niepotrzebnie.
W przypadku linii 19, czyli metody orElseGet
również zostaje zwrócony opakowany napis "javowiec"
. Jednakże metoda getDefault()
nie zostaje wykonana.

getDefault
zawarta w metodzie orElse
zostaje wykonana pomimo opakowywania obiektu test
przez Optional. Metoda orElseGet
w tym wypadku nie wywołuje metody getDefault
Z kolei po zmianie zmiennej testowej na null
widzimy, że w obu przypadkach jest wywoływana metoda getDefault()
i zwracany domyślny napis "default"
. Tym razem wykonanie metody getDefault()
było konieczne.

getDefault
zawarta w metodzie orElse
zostaje wykonana także gdy Optional jest pusty. Metoda orElseGet
w tym wypadku również wywołuje metodę getDefault
GitHub
Kod źródłowy jest udostępniony na GitHubie. Poniżej link 🙂
Podsumowanie
Zwracaj Optional<T>
z metody wtedy, gdy w pewnych okolicznościach nie może zwrócić obiektu T
i klient będzie musiał ten przypadek obsłużyć dodatkowym kodem. Kolekcje, mapy, streamy, tablice i inne typy kontenerowe (w tym Optionale) zwracamy puste zamiast opakowywać je w Optionale.
Należy pamiętać, że jeżeli przekażemy null
do metody of
tworzącej Optional, zostanie wyrzucony wyjątek NullPointerException
. Natomiast metoda get
wywołana na pustym Optionalu wyrzuci wyjątek NoSuchElementException
.
Preferowanymi sposobami zwracania opakowanego obiektu T
z Optionala jest użycie metod orElse
lub orElseGet
. Metodę orElse
używamy tylko wtedy, gdy posiadamy już stworzony obiekt. Jeżeli w parametrze zawrzemy metodę, która zwraca obiekt, metoda ta zostanie zawsze wykonana. Nawet, jeśli Optional nie jest pusty. Jeżeli nie posiadamy jeszcze gotowego obiektu (np. trzeba go ściągnąć z Bazy Danych lub stworzyć), należy użyć metody orElseGet
. Metoda ta gwarantuje, że metoda interfejsu funkcyjnego Supplier
zostanie wywołana tylko wtedy, gdy Optional jest pusty.
Liczba komentarzy:
Zaloguj się lub dołącz do społeczności Javowców, aby móc uczestniczyć w dyskusji 🙂