Problem, który rozwiązuje Optional
Metoda, która może nie mieć odpowiedzi, zawsze stawiała w Javie niewygodny wybór: zwrócić null i liczyć na to, że wywołujący pamięta o sprawdzeniu. Zwykle nie pamięta, a skutkiem jest NullPointerException, który pojawia się daleko od metody, która zwróciła null.
Optional<T> to małe pudełko, które albo zawiera wartość, albo jest jawnie puste. Zwracając Optional<User> zamiast User, metoda mówi wywołującym w swojej sygnaturze: "mogę nic nie znaleźć", a kompilator popycha ich do obsłużenia tego przypadku.
Tworzenie Optional
Są trzy metody fabrykujące i wybór właściwej ma znaczenie:
Optional.of(value): wartość nie może być null, w przeciwnym razie od razu rzucaNullPointerException.Optional.ofNullable(value): pusty, jeśli wartość to null, w przeciwnym razie z wartością. Używaj go do opakowania czegoś, co może być null.Optional.empty(): pusty Optional.
Częsty błąd to sięgnięcie po Optional.of(x) dla wartości, która może być null. To niweczy cały sens i rzuca dokładnie ten wyjątek, którego chcesz uniknąć. W razie wątpliwości użyj ofNullable.
Bezpieczny odczyt wartości
Gdy masz już Optional, celem jest wyciągnięcie wartości bez zakładania, że tam jest. Toporne narzędzie to get() i prawie nigdy nie należy go używać:
Optional<String> name = Optional.empty();
String value = name.get(); // rzuca NoSuchElementException: to tylko przebrany NPE
Zamiast tego podaj wartość zapasową albo zareaguj na obecność wartości. orElse zwraca wartość domyślną, gdy Optional jest pusty; ifPresent uruchamia kod tylko wtedy, gdy wartość istnieje:
Użyj orElseGet(supplier) zamiast orElse, gdy zbudowanie wartości domyślnej jest kosztowne: supplier uruchamia się tylko wtedy, gdy Optional rzeczywiście jest pusty. Argument orElse jest obliczany zawsze, nawet gdy nie jest potrzebny.
orElseThrow, gdy "to powinno istnieć"
Czasem brak wartości naprawdę jest błędem: wymagana wartość konfiguracji albo użytkownik, który powinien być w bazie danych. orElseThrow zamienia pusty Optional w jasny wyjątek wybranego przez ciebie typu:
Czyta się to dużo lepiej niż blok if (opt.isPresent()) i pokazuje błąd wprost w miejscu, w którym występuje.
Przekształcanie przez map i filter
Prawdziwa korzyść to łączenie w łańcuchy. map stosuje funkcję do wartości tylko wtedy, gdy ona istnieje, a pusty Optional zostawia pustym, więc przekształcasz dane, nigdy nie dotykając null. filter odrzuca wartość, jeśli nie przejdzie testu.
Jeśli funkcja mapująca sama zwraca Optional, użyj flatMap zamiast map, żeby uniknąć niezgrabnego Optional<Optional<T>>. To odpowiada rozróżnieniu map/flatMap znanemu ze strumieni: Optional zachowuje się podobnie jak strumień zawierający zero albo jeden element.
Gdzie Optional pasuje (a gdzie nie)
Optional zaprojektowano jako typ zwracany przez metody, które zgodnie z zasadami mogą nie dać wyniku. Klasyczne przykłady to Stream.findFirst(), wyszukiwania w stylu Map i parsowanie. Używaj go tam, a twoje API samo będzie dokumentować swoje luki.
Nie jest przeznaczony do:
- Pól. Nie jest serializowalny i dodaje osobny obiekt na każde pole. Używaj zwykłych referencji i sprawdzaj je w konstruktorze.
- Parametrów metod. Wywołujący musieliby wtedy opakowywać argumenty w
Optional.of(...), co jest bardziej uciążliwe niż przeciążenie albo parametr dopuszczający null. - Kolekcji. Zwracaj pustą
List, a nieOptional<List>. Pusta kolekcja już oznacza "nic".
Traktowany jako typ zwracany, Optional zamienia ciche błędy z null w podpowiedzi na etapie kompilacji, by obsłużyć brakujący przypadek.
Dalej: wyjątki
Optional zgrabnie obsługuje codzienny przypadek "wartości może nie być", ale niektóre błędy są naprawdę wyjątkowe: plik, którego nie da się otworzyć, zerwane połączenie sieciowe, dane, których nie da się sparsować. Java modeluje je wyjątkami i o nich jest następna strona.
Najczęściej zadawane pytania
Czym jest Optional w Javie i po co go używać?
Optional<T> to kontener, który albo zawiera wartość, albo jest pusty. Istnieje po to, by "wartości może nie być" było widać wprost w typie zwracanym metody. Dzięki temu wywołujący są skłaniani do obsłużenia pustego przypadku, zamiast zapomnieć o sprawdzeniu null i dostać NullPointerException w czasie działania. Używaj go głównie jako typu zwracanego przez metody, które mogą nie dać wyniku, np. wyszukiwanie, które nic nie znajduje.
Czym różni się Optional.of od Optional.ofNullable?
Optional.of(x) od razu rzuca NullPointerException, jeśli x to null; używaj go, gdy wiesz, że wartość nie jest null. Optional.ofNullable(x) zwraca pusty Optional, gdy x to null, a w przeciwnym razie Optional z wartością; używaj go, gdy wartość może być null. Optional.empty() daje bezpośrednio pusty Optional.
Czy do odczytu wartości używać Optional.get()?
Unikaj gołego get(): rzuca NoSuchElementException, gdy Optional jest pusty, czyli to po prostu NullPointerException pod inną nazwą. Wybieraj orElse, orElseGet, orElseThrow, ifPresent albo map, żeby pusty przypadek zawsze był obsłużony. Jeśli musisz najpierw sprawdzić, zabezpiecz się przez isPresent(), ale metody funkcyjne są czytelniejsze.