Niedawno przenieśliśmy się na Javę 8. Teraz widzę aplikacje zalane Optional
obiektami.
Przed Java 8 (styl 1)
Employee employee = employeeServive.getEmployee();
if(employee!=null){
System.out.println(employee.getId());
}
Po Javie 8 (styl 2)
Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
Employee employee = employeeOptional.get();
System.out.println(employee.getId());
}
Nie widzę żadnej wartości dodanej, Optional<Employee> employeeOptional = employeeService.getEmployee();
gdy sama usługa zwraca opcjonalną:
Pochodząc z tła Java 6, widzę Styl 1 jako bardziej przejrzysty i z mniejszą liczbą linii kodu. Czy brakuje mi tutaj jakiejś realnej korzyści?
Skonsolidowane ze zrozumienia wszystkich odpowiedzi i dalszych badań na blogu
employeeOptional.isPresent()
wydaje się, że całkowicie nie ma sensu typów opcji. Zgodnie z komentarzem @ MikePartridge nie jest to zalecane ani idiomatyczne. Jawne sprawdzanie, czy typ opcji jest czymś, czy niczym, jest nie-nie. Powinieneś je po prostumap
lubflatMap
nad nimi.Optional
Odpowiedzi:
Styl 2 nie obsługuje Java 8, aby w pełni zobaczyć zalety. W ogóle nie chcesz
if ... use
. Zobacz przykłady Oracle . Korzystając z ich rad, otrzymujemy:Styl 3
Lub dłuższy fragment
źródło
void ifPresent(Consumer<T>, Runnable)
, opowiadałbym się za całkowitym zakazemisPresent
iget
ifPresentOrElse(Consumer<? super T>, Runnable)
.Jeśli używasz
Optional
jako warstwy „zgodności” między starszym interfejsem API, który może nadal zwracaćnull
, pomocne może być utworzenie (niepustej) Opcjonalnej na ostatnim etapie, aby mieć pewność , że coś masz. Np. Gdzie napisałeś:Zdecydowałbym się na:
Chodzi o to, aby wiedzieć , że istnieje niezerowe pracownik usług , więc można owinąć się w sposób, który
Optional
zOptional.of()
. Następnie, gdy do tego zadzwoniszgetEmployee()
, możesz, ale nie musisz, zatrudnić pracownika. Ten pracownik może (lub być może nie musi) mieć dokumentu tożsamości. Następnie, jeśli skończyłeś z identyfikatorem, chcesz go wydrukować.Nie ma potrzeby jawnego sprawdzania, czy w kodzie nie ma żadnych wartości null, obecności itp.
źródło
(...).ifPresent(aMethod)
zif((...).isPresent()) { aMethod(...); }
? Wydaje się, że jest to po prostu kolejna składnia do wykonania prawie tej samej operacji.Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet);
Teraz mogę przetwarzaćchildren
jednolicie, npchildren.forEach(relative::sendGift);
. Te mogą także być łączone:Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet).forEach(relative::sendGift);
. Cała płyta kontrolna zerowania itp., ...Posiadanie
Optional
jednej wartości nie ma prawie żadnej wartości dodanej . Jak widzieliście, to po prostu zastępuje czek nanull
czek na obecność.Posiadanie takich rzeczy ma ogromną wartość dodaną
Collection
. Wszystkie metody przesyłania strumieniowego wprowadzone w celu zastąpienia pętli niskopoziomowych w starym stylu rozumiejąOptional
rzeczy i robią to, co należy (nie przetwarzając ich lub ostatecznie zwracając inny zestawOptional
). Jeśli masz do czynienia z n przedmiotami częściej niż z pojedynczymi przedmiotami (i robi to 99% wszystkich realistycznych programów), ogromną poprawą jest wbudowane wsparcie dla opcjonalnie prezentowanych rzeczy. W idealnym przypadku nigdy nie musisz sprawdzać obecnościnull
ani obecności.źródło
Employee getEmployee()
Vs.Optional<Employee> getEmployee()
Choć technicznie oba mogą powrócićnull
, jeden z nich jest znacznie bardziej narażony na problemy..map()
sprawdzania wartości zerowej po drodze jest świetna. NpOptional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);
.null
.Tak długo, jak używasz
Optional
fantazyjnego interfejsu APIisNotNull()
, tak, nie znajdziesz żadnych różnic w samym sprawdzaniunull
.Rzeczy, których nie powinno się używać
Optional
doOptional
Bad Code ™ używa tylko do sprawdzenia obecności wartości:Co należy używać
Optional
doUnikaj nieobecności wartości, tworząc ją w razie potrzeby, gdy używasz metod API zwracających wartość zerową:
Lub, jeśli utworzenie nowego pracownika jest zbyt kosztowne:
Ponadto, uświadamiając wszystkim, że twoje metody mogą nie zwrócić wartości (jeśli z jakiegokolwiek powodu nie możesz zwrócić wartości domyślnej jak wyżej):
I na koniec, istnieją wszystkie podobne do strumieni metody, które zostały już wyjaśnione w innych odpowiedziach, chociaż tak naprawdę nie decydujesz się ich użyć,
Optional
ale korzystasz zOptional
dostarczonych przez kogoś innego.źródło
Optional
że to stracona szansa. „Tutaj, mam tę nowąOptional
rzecz, którą stworzyłem, abyście musieli sprawdzić wartości zerowe. A tak przy okazji… dodałemget()
do niej metodę, abyście nie musieli niczego sprawdzać;);)” . (...)Optional
jest fajny jak neonowy znak „TO MOŻE BYĆ NULL”, ale samo istnienie tejget()
metody go kaleczy » . Ale OP prosiło o powody użyciaOptional
, a nie powody przeciwne lub wady projektowe.Employee employee = Optional.ofNullable(employeeService.getEmployee());
W trzecim fragmencie nie powinien być ten typOptional<Employee>
?get
jest konieczność interakcji ze starszym kodem. Nie mogę wymyślić wielu ważnych powodów, dla których nowy kod miałby kiedykolwiek być używanyget
. To powiedziawszy, podczas interakcji ze starszym kodem często nie ma alternatywy, ale wciąż Walen ma rację, że istnienieget
początkujących powoduje, że początkujący kod pisze zły kod.Map
ani razu i nie znam nikogo, kto dokonałby tak drastycznej, niezgodnej wstecz, zmiany, więc upewnij się, że możesz celowo zaprojektować złe interfejsy APIOptional
- nie jestem pewien, co to dowodzi. DlaOptional
niektórychtryGet
metod miałoby to jednak sens .Map
to przykład, który wszyscy znają. Oczywiście nie mogą uczynićMap.get
zwrotu opcjonalnym ze względu na kompatybilność wsteczną, ale można łatwo wyobrazić sobie inną klasę podobną do mapy, która nie miałaby takich ograniczeń kompatybilności. Powiedziećclass UserRepository {Optional<User> getUserDetails(int userID) {...}}
. Teraz chcesz uzyskać dane zalogowanego użytkownika i wiesz, że istnieją, ponieważ udało im się zalogować, a system nigdy nie usuwa użytkowników. Więc tytheUserRepository.getUserDetails(loggedInUserID).get()
.Kluczową kwestią dla mnie w korzystaniu z Opcjonalnego jest wyjaśnienie, kiedy programista musi sprawdzić, czy funkcja zwraca wartość, czy nie.
źródło
Twoim głównym błędem jest to, że nadal myślisz w kategoriach proceduralnych. To nie jest krytyka ciebie jako osoby, to tylko spostrzeżenie. Myślenie bardziej funkcjonalnymi terminami wiąże się z czasem i praktyką, dlatego metody są prezentowane i wyglądają jak najbardziej oczywiste, poprawne rzeczy, do których można zadzwonić. Twoim drugorzędnym drobnym błędem jest utworzenie Opcjonalnej w ramach metody. Opcjonalny ma na celu pomóc udokumentować, że coś może, ale nie musi, zwrócić wartość. Możesz nic nie dostać.
Doprowadziło to do napisania doskonale czytelnego kodu, który wydaje się całkowicie rozsądny, ale uwiodły cię podłe, kuszące bliźniaczki, które dostały i są obecne.
Oczywiście szybko pojawia się pytanie „dlaczego są obecne i nawet się tam dostać?”
Wielu ludzi tutaj tęskni za tym, że isPresent () jest to, że nie jest to coś, co zostało stworzone dla nowego kodu napisanego przez ludzi w pełni zaangażowanych w to, jak cholernie przydatne są lambdas i którzy lubią funkcjonalność.
Daje nam jednak kilka (dwóch) dobrych, wspaniałych, uroczych (?) Korzyści:
Pierwszy jest raczej prosty.
Wyobraź sobie, że masz interfejs API, który wyglądał tak:
I miałeś klasę klasyczną, używając tego jako takiego (wypełnij puste pola):
Na pewno wymyślony przykład. Ale bądźcie ze mną tutaj.
Java 8 została uruchomiona i staramy się wejść na pokład. Jedną z rzeczy, które robimy, jest zamiana naszego starego interfejsu na coś, co zwraca Opcjonalne. Dlaczego? Ponieważ, jak ktoś już wspominał łaskawie: eliminuje to zgadywanie, czy coś może być zerowe . Inni zwracali już na to uwagę. Ale teraz mamy problem. Wyobraźmy sobie, że mamy (przepraszam, gdy nacisnąłem Alt + F7 przy niewinnej metodzie), 46 miejsc, w których ta metoda jest wywoływana w dobrze przetestowanym starszym kodzie, który w przeciwnym razie świetnie sobie radzi. Teraz musisz zaktualizować je wszystkie.
TO jest, gdzie isPresent świeci.
Ponieważ teraz: Snickers lastSnickers = snickersCounter.lastConsumedSnickers (); if (null == lastSnickers) {wyrzuć nowy NoSuchSnickersException (); } else {Consumer.giveDiabetes (lastSnickers); }
staje się:
Jest to prosta zmiana, którą możesz dać nowemu juniorowi: może zrobić coś pożytecznego, a jednocześnie będzie mógł eksplorować bazę kodów. win-win. W końcu coś podobnego do tego wzoru jest dość rozpowszechnione. A teraz nie musisz przepisywać kodu, aby używać lambd lub czegokolwiek. (W tym konkretnym przypadku byłoby to trywialne, ale zostawiam wymyślanie przykładów, w których byłoby to trudne dla czytelnika).
Zauważ, że oznacza to, że sposób, w jaki to zrobiłeś, jest zasadniczo sposobem radzenia sobie ze starszym kodem bez wykonywania kosztownych przeróbek. A co z nowym kodem?
W twoim przypadku, gdy chcesz po prostu coś wydrukować, po prostu zrobiłbyś:
snickersCounter.lastConsumedSnickers (). ifPresent (System.out :: println);
Co jest dość proste i całkowicie jasne. Problem, który powoli wypływa na powierzchnię, polega na tym, że istnieją przypadki użycia dla get () i isPresent (). Służą do mechanicznej modyfikacji istniejącego kodu w celu użycia nowszych typów bez zbytniego zastanawiania się nad tym. To, co robisz, jest zatem mylone na następujące sposoby:
Jeśli chcesz użyć Opcjonalnego jako prostej kontroli bezpieczeństwa zerowego, powinieneś to zrobić po prostu:
Oczywiście dobrze wyglądająca wersja tego wygląda następująco:
Nawiasem mówiąc, chociaż nie jest to w żaden sposób wymagane, zalecam użycie nowej linii na operację, aby łatwiej było ją odczytać. Łatwy do odczytania i zrozumienia bije zwięzłość każdego dnia tygodnia.
Jest to oczywiście bardzo prosty przykład, w którym łatwo zrozumieć wszystko, co próbujemy zrobić. W życiu nie zawsze jest to takie proste. Ale zauważ, jak w tym przykładzie wyrażamy nasze intencje. Chcemy UZYSKAĆ pracownika, UZYSKAĆ jego identyfikator i, jeśli to możliwe, wydrukować go. To druga duża wygrana z Opcjonalnym. Pozwala nam tworzyć wyraźniejszy kod. Myślę też, że robienie rzeczy takich jak tworzenie metody, która robi mnóstwo rzeczy, aby można było nakarmić ją mapą, jest ogólnie dobrym pomysłem.
źródło
map(x).ifPresent(f)
po prostu nie ma tego samego intuicyjnego sensu, coif(o != null) f(x)
ma.if
Wersja jest zupełnie jasne. To kolejny przypadek ludzi skaczących na popularny wagon-band, * nie * przypadek prawdziwego postępu.Optional
. Odniosłem się tylko do użycia opcjonalnego w tym konkretnym przypadku, a bardziej do czytelności, ponieważ wiele osób zachowuje się tak, jakby ten format był tak samo lub bardziej czytelny niżif
.isPresent
iget
na tyle, na ile jest to realistycznie możliwe), poprawić bezpieczeństwo . Trudniej jest napisać niepoprawny kod, który nie sprawdza braku wartości, jeśli typ jestOptional<Whatever>
raczej niż nullWhatever
.get
, będziesz zmuszony wybrać, co zrobić, gdy pojawi się brakująca wartość: użyjeszorElse()
(luborElseGet()
), aby podać wartość domyślną, luborElseThrow()
wyrzucić wybraną wyjątek dokumentujący naturę problemu , który jest lepszy niż ogólny NPE, który nie ma znaczenia, dopóki nie zagłębisz się w ślad stosu.Nie widzę korzyści w stylu 2. Wciąż musisz zdawać sobie sprawę, że potrzebujesz kontroli zerowej, a teraz jest ona jeszcze większa, a więc mniej czytelna.
Lepszym stylem byłoby, gdyby pracownikServive.getEmployee () zwrócił Opcjonalne, a kod stałby się:
W ten sposób nie możesz wywołaćemplemployeeServive.getEmployee (). GetId () i zauważysz, że powinieneś jakoś obsługiwać opcjonalne wartości. A jeśli w całej twojej bazie kodu metody nigdy nie zwracają wartości zerowej (lub prawie nigdy z dobrze znanymi wyjątkami dla twojej drużyny od tej reguły), zwiększy to bezpieczeństwo przeciwko NPE.
źródło
isPresent()
. Używamy opcjonalnie, więc nie musimy testować.isPresent()
. To niewłaściwe podejście do opcji. Użyjmap
lubflatMap
zamiast.ifPresent
) to tylko kolejny sposób na przetestowanie.ifPresent(x)
to tak samo test jakif(present) {x}
. Zwracana wartość nie ma znaczenia.Myślę, że największą korzyścią jest to, że jeśli sygnatura metody zwraca an
Employee
, nie sprawdza się wartości null. Dzięki temu podpisowi wiesz, że powrót pracownika jest gwarantowany. Nie musisz zajmować się przypadkiem awarii. Opcjonalnie wiesz, że tak.W kodzie, który widziałem, są kontrole zerowe, które nigdy nie zawiodą, ponieważ ludzie nie chcą przeszukiwać kodu w celu ustalenia, czy wartość zerowa jest możliwa, więc rzucają wszędzie kontrole zerowe. To sprawia, że kod jest nieco wolniejszy, ale co ważniejsze, sprawia, że kod jest znacznie głośniejszy.
Jednak aby to zadziałało, musisz konsekwentnie stosować ten wzorzec.
źródło
@Nullable
i@ReturnValuesAreNonnullByDefault
wraz z analizą statyczną.package-info.java
ze@ReturnValuesAreNonnullByDefault
wszystkich moich paczek, więc wszystko co musisz zrobić, to dodać@Nullable
, gdzie trzeba, aby przełączyćOptional
. Oznacza to mniej znaków, brak dodanych śmieci i brak narzutu w czasie wykonywania. Nie muszę przeglądać dokumentacji, która pokazuje, kiedy najedziesz kursorem na metodę. Narzędzie analizy statycznej wyłapuje błędy, a później zmiany nullability.Optional
jest to, że dodaje alternatywny sposób radzenia sobie z zerowalnością. Jeśli był w Javie od samego początku, może być w porządku, ale teraz to tylko ból. Idąc drogą Kotlin, byłoby o wiele lepiej IMHO. +++ Zauważ, żeList<@Nullable String>
wciąż jest to lista ciągów, aList<Optional<String>>
nie jest.Moja odpowiedź brzmi: po prostu nie. Pomyśl przynajmniej dwa razy, czy to naprawdę poprawa.
Wyrażenie (wzięte z innej odpowiedzi) jak
wydaje się być właściwym funkcjonalnym sposobem radzenia sobie z pierwotnie zerowalnymi metodami zwracania. Wygląda ładnie, nigdy się nie rzuca i nigdy nie sprawia, że myślisz o obsłudze „nieobecnej” sprawy.
Wygląda lepiej niż w starym stylu
co czyni oczywistym, że mogą istnieć
else
klauzule.Funkcjonalny styl
orElse
nie zawsze jest wystarczający)W żadnym wypadku nie należy go stosować jako panaceum. Jeśli miałoby to uniwersalne zastosowanie, semantykę
.
operatora należy zastąpić semantyką?.
operatora , zapomnieć o NPE i wszystkie problemy zignorować.Będąc wielkim fanem Javy, muszę powiedzieć, że styl funkcjonalny jest po prostu zamiennikiem tego stwierdzenia przez biednego człowieka Javy
dobrze znany z innych języków. Czy zgadzamy się, że to oświadczenie
źródło
employeeService.getEmployee().getId().apply(id => System.out.println(id));
i upewnij się, że jest on zerowo bezpieczny raz, używając operatora Bezpiecznej nawigacji i razOptional
. Oczywiście możemy to nazwać „szczegółem wdrożenia”, ale wdrożenie ma znaczenie. Są przypadki, gdy lamdy upraszczają i upraszczają kod, ale tutaj jest to po prostu brzydkie nadużycie.Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println);
funkcja Język:employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));
. Dwie składnie, ale wyglądają bardzo podobnie do mnie. „Pojęcie” toOptional
akaNullable
akaMaybe
+++
Nadużywane są lamdy do radzenia sobie z zerowalnością. Lamdy są w porządku, aleOptional
nie są. Nullability potrzebuje po prostu odpowiedniego wsparcia językowego, jak w Kotlin. Kolejny problem:List<Optional<String>>
nie ma związku z tymList<String>
, gdzie powinniśmy być powiązani.Opcjonalna jest koncepcja (typ wyższego rzędu) pochodząca z programowania funkcjonalnego. Użycie go eliminuje zagnieżdżone sprawdzanie zerowania i ratuje cię przed piramidą zagłady.
źródło