mainejug.org

Praktyczna wiedza na co dzień

mainejug.org

Praktyczna wiedza na co dzień

Java

Optional w Javie: jak używać go poprawnie i czego unikać

Klasa Optional, dostępna w Javie od wersji 8, miała pomóc w walce z wyjątkiem NullPointerException. Dobrze użyta robi kod czytelniejszym, ale w praktyce widuje się ją w miejscach, w których tylko zaciemnia obraz. Ten wpis pokazuje, do czego Optional został stworzony, jak z niego korzystać i jakich pułapek unikać.

Po co powstał Optional

Metody, które mogą nie zwrócić wyniku, od zawsze zwracały null. Problem w tym, że sygnatura metody nie mówi wprost, czy wolno się go spodziewać. Programista, który zapomni o sprawdzeniu, dostaje błąd w czasie działania programu. Optional ma zakomunikować w typie zwracanym: ta wartość może nie istnieć, więc musisz to obsłużyć.

Najlepiej sprawdza się jako typ zwracany przez metody, które czasem nie mają sensownego wyniku, na przykład wyszukiwanie po identyfikatorze:

Optional<User> findById(long id) {
    return Optional.ofNullable(repository.get(id));
}

Wywołujący od razu widzi, że użytkownika może nie być, i musi zdecydować, co wtedy zrobić.

Tworzenie i odczytywanie wartości

Do utworzenia obiektu służą trzy metody fabryczne:

  • Optional.of(wartosc) tworzy Optional z wartością, która nie może być nullem; w przeciwnym razie zgłasza wyjątek,
  • Optional.ofNullable(wartosc) akceptuje null i zwraca pusty Optional,
  • Optional.empty() zwraca pusty obiekt.

Do odczytu wartości lepiej unikać metody get() bez wcześniejszego sprawdzenia, ponieważ w razie braku wartości rzuci wyjątek, co niczym nie różni się od klasycznego błędu z nullem. Zamiast tego używaj bezpieczniejszych odpowiedników:

String imie = findById(5)
        .map(User::getName)
        .orElse("nieznany");

User user = findById(5)
        .orElseThrow(() -> new IllegalArgumentException("Brak użytkownika"));

Metoda orElse zwraca wartość domyślną, a orElseThrow pozwala wyrazić, że brak wartości to sytuacja błędna. Od Javy 10 dostępna jest też bezargumentowa wersja orElseThrow().

Przetwarzanie w stylu funkcyjnym

Siła Optionala tkwi w łączeniu operacji bez zagnieżdżonych instrukcji if. Metoda map przekształca wartość, jeśli istnieje, filter zostawia ją tylko wtedy, gdy spełnia warunek, a ifPresent wykonuje akcję tylko dla istniejącej wartości:

findById(5)
        .filter(User::isActive)
        .ifPresent(u -> sendWelcome(u));

Gdy trzeba obsłużyć obie sytuacje, od Javy 9 możesz użyć ifPresentOrElse. Jeśli funkcja przekształcająca sama zwraca Optional, wybierz flatMap, żeby uniknąć konstrukcji Optional<Optional<T>>.

Częste błędy

Optional nie jest remedium na wszystko. Najczęściej powtarzające się błędy to:

  1. Optional jako pole klasy. Nie został zaprojektowany do przechowywania stanu, nie jest serializowalny i zwiększa zużycie pamięci. Pole lepiej zostawić jako zwykłe, a Optional zwracać z gettera, jeśli to ma sens.
  2. Optional jako parametr metody. Zmusza wywołującego do opakowywania wartości i nie zapobiega przekazaniu nulla. Prostszym rozwiązaniem bywa przeciążenie metody.
  3. Zwracanie nulla zamiast Optionala. Metoda, która deklaruje zwrot Optionala, nigdy nie powinna zwracać null.
  4. Użycie isPresent() razem z get(). To tylko stary kod z nullem w nowym opakowaniu. Zamiast tego użyj map, orElse lub ifPresent.
  5. Optional dla kolekcji. Zamiast Optional<List<T>> zwracaj po prostu pustą listę.

Wydajność i kontekst użycia

Każdy Optional to dodatkowy obiekt, więc w gorących pętlach, przetwarzających miliony elementów, bywa to odczuwalne. W typowych aplikacjach biznesowych koszt jest pomijalny, ale warto o nim pamiętać przy kodzie krytycznym wydajnościowo. Dla typów prostych istnieją wyspecjalizowane warianty: OptionalInt, OptionalLong i OptionalDouble, które unikają opakowywania liczb.

Optional dobrze współpracuje ze strumieniami. Metody takie jak findFirst() czy max() zwracają Optional, dzięki czemu można wygodnie obsłużyć przypadek pustego strumienia:

String najdluzszy = slowa.stream()
        .max(Comparator.comparingInt(String::length))
        .orElse("");

Podsumowanie

Optional to narzędzie do jednego głównego celu: jasnego komunikowania, że wynik metody może być nieobecny. Używaj go w typach zwracanych, łącz operacje przez map, filter i orElse, a unikaj w polach, parametrach i kolekcjach. Jeśli w projekcie masz wiele zagnieżdżonych sprawdzeń nulla, spróbuj zmienić jedną metodę wyszukującą na zwracającą Optional i zobacz, jak zmienia się czytelność kodu wokół niej.

Na koniec praktyczna uwaga dotycząca testów. Metody zwracające Optional są łatwe do sprawdzenia: wystarczy napisać jeden test dla przypadku z wartością i jeden dla przypadku pustego wyniku. Dzięki temu obie ścieżki są jawnie pokryte, a zmiana kontraktu metody, na przykład z zawsze obecnej wartości na opcjonalną, od razu wymusi poprawki w miejscach użycia. To jeden z cichych, ale realnych zysków z tego podejścia w większych projektach, nad którymi pracuje wiele osób.

https://study-abroad.pl/kompleksowa-opieka-dla-starszych-co-wyroznia-luxmed-pakiet-rodzinny/ | https://ostnachrichtenblog.de/terrassenuberdachung-aus-holz-glas-oder-aluminium-was-ist-die-beste-wahl/ | https://balltraps.com/pakiety-medyczne-dla-firm-zwieksz-lojalnosc-i-satysfakcje-pracownikow/