Dlaczego warto korzystać z opcji Opcjonalne w Javie 8+ zamiast tradycyjnych sprawdzania wskaźnika zerowego?

110

Niedawno przenieśliśmy się na Javę 8. Teraz widzę aplikacje zalane Optionalobiektami.

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

użytkownik3198603
źródło
28
Fuj! 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 prostu maplub flatMapnad nimi.
Andres F.,
7
Zobacz przepełnienie stosu Odpowiedź na przez Brian Goetz , z Architect języka Java w Oracle. Optional
Basil Bourque,
6
Tak dzieje się, gdy wyłączysz mózg w imię „najlepszych praktyk”.
immibis
9
To dość słabe użycie opcjonalnych, ale nawet to ma zalety. W przypadku wartości zerowych wszystko może być zerowe, więc musisz ciągle sprawdzać, opcjonalnie woła, że ​​tej konkretnej zmiennej prawdopodobnie brakuje, upewnij się, że ją sprawdziłeś
Richard Tingle

Odpowiedzi:

108

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

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

Lub dłuższy fragment

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));
Caleth
źródło
19
faktycznie zakazujemy większości zastosowań isPresent (przechwytywanych przez przegląd kodu)
jk.
10
@jk. rzeczywiście, gdyby nastąpiło przeciążenie void ifPresent(Consumer<T>, Runnable), opowiadałbym się za całkowitym zakazem isPresentiget
Caleth
10
@Caleth: Możesz być zainteresowany Javą 9 ifPresentOrElse(Consumer<? super T>, Runnable).
wchargin
9
Mam jedną wadę w tym stylu java 8, w porównaniu do java 6: oszukuje narzędzia do pokrywania kodu. Z instrukcją if pokazuje, kiedy przegapiłeś oddział, a nie tutaj.
Thierry
14
@Aaron „drastycznie zmniejsza czytelność kodu”. Która część dokładnie zmniejsza czytelność? Brzmi bardziej jak „zawsze robiłem to inaczej i nie chcę zmieniać swoich nawyków” niż obiektywne rozumowanie.
Voo,
47

Jeśli używasz Optionaljako 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ś:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

Zdecydowałbym się na:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

Chodzi o to, aby wiedzieć , że istnieje niezerowe pracownik usług , więc można owinąć się w sposób, który Optionalz Optional.of(). Następnie, gdy do tego zadzwonisz getEmployee(), 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.

Joshua Taylor
źródło
4
Jaka jest zaleta korzystania (...).ifPresent(aMethod)z if((...).isPresent()) { aMethod(...); }? Wydaje się, że jest to po prostu kolejna składnia do wykonania prawie tej samej operacji.
Ruslan
2
@ Ruslan Po co używać jednej specjalnej składni dla jednego konkretnego połączenia, skoro masz już ogólne rozwiązanie, które działa równie dobrze we wszystkich przypadkach? Stosujemy również ten sam pomysł z mapy i współpracy.
Voo,
1
@ Ruslan ... interfejs API zwraca wartości null zamiast pustych kolekcji lub tablic. Np. Możesz zrobić coś takiego (zakładając, że klasa Parent, dla której getChildren () zwraca zestaw dzieci, lub null, jeśli nie ma dzieci): Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet); Teraz mogę przetwarzać childrenjednolicie, np children.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., ...
Joshua Taylor
1
@ Ruslan ... jest delegowany na wypróbowany i prawdziwy kod w standardowej bibliotece. Zmniejsza to ilość kodu, który jako programista muszę pisać, testować, debugować itp.
Joshua Taylor
1
Cieszę się, że używam Swift. jeśli pozwól pracownikowi = workerService.employee {print (pracownik.Id); }
gnasher729
23

Posiadanie Optionaljednej wartości nie ma prawie żadnej wartości dodanej . Jak widzieliście, to po prostu zastępuje czek na nullczek 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ą Optionalrzeczy i robią to, co należy (nie przetwarzając ich lub ostatecznie zwracając inny zestaw Optional). 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ści null ani obecności.

Kilian Foth
źródło
24
Oprócz kolekcji / strumieni opcja jest również świetna, aby interfejsy API były bardziej samodokumentujące, np . Employee getEmployee()Vs. Optional<Employee> getEmployee()Choć technicznie oba mogą powrócić null, jeden z nich jest znacznie bardziej narażony na problemy.
amon
16
Opcjonalnie pojedyncza wartość ma dużą wartość. Możliwość ciągłego .map()sprawdzania wartości zerowej po drodze jest świetna. Np Optional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);.
Joshua Taylor
9
Nie zgadzam się z twoim początkowym komentarzem bez wartości dodanej. Opcjonalnie zapewnia kontekst dla twojego kodu. Szybki rzut oka może wyjaśnić poprawność poprawności funkcji i wszystkie przypadki są objęte gwarancją. Podczas gdy przy zerowym sprawdzaniu, czy powinieneś sprawdzać zerowo każdy pojedynczy typ referencyjny dla każdej pojedynczej metody? Podobny przypadek można zastosować do klas i modyfikatorów publicznych / prywatnych; dodają zerową wartość do twojego kodu, prawda?
ArTs
3
@JoshuaTaylor Szczerze, w porównaniu do tego wyrażenia wolę staroświecki czek na null.
Kilian Foth,
2
Kolejną dużą zaletą Opcjonalnego, nawet w przypadku pojedynczych wartości, jest to, że nie można zapomnieć o sprawdzeniu wartości null, kiedy należy to zrobić. W przeciwnym razie bardzo łatwo zapomnieć o czeku i skończyć z NullPointerException ...
Sean Burton
17

Tak długo, jak używasz Optionalfantazyjnego interfejsu API isNotNull(), tak, nie znajdziesz żadnych różnic w samym sprawdzaniu null.

Rzeczy, których nie powinno się używać Optionaldo

OptionalBad Code ™ używa tylko do sprawdzenia obecności wartości:

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

Co należy używać Optionaldo

Unikaj nieobecności wartości, tworząc ją w razie potrzeby, gdy używasz metod API zwracających wartość zerową:

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

Lub, jeśli utworzenie nowego pracownika jest zbyt kosztowne:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

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):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

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ć, Optionalale korzystasz z Optionaldostarczonych przez kogoś innego.

Walen
źródło
1
@Kapep Rzeczywiście. Mieliśmy tę debatę na / r / java i powiedziałem : «Widzę, Optionalże to stracona szansa. „Tutaj, mam tę nową Optionalrzecz, którą stworzyłem, abyście musieli sprawdzić wartości zerowe. A tak przy okazji… dodałem get()do niej metodę, abyście nie musieli niczego sprawdzać;);)” . (...) Optionaljest fajny jak neonowy znak „TO MOŻE BYĆ NULL”, ale samo istnienie tej get()metody go kaleczy » . Ale OP prosiło o powody użycia Optional, a nie powody przeciwne lub wady projektowe.
wal
1
Employee employee = Optional.ofNullable(employeeService.getEmployee());W trzecim fragmencie nie powinien być ten typ Optional<Employee>?
Charlie Harding
2
@immibis W jaki sposób okaleczony? Głównym powodem korzystania z niego getjest konieczność interakcji ze starszym kodem. Nie mogę wymyślić wielu ważnych powodów, dla których nowy kod miałby kiedykolwiek być używany get. To powiedziawszy, podczas interakcji ze starszym kodem często nie ma alternatywy, ale wciąż Walen ma rację, że istnienie getpoczątkujących powoduje, że początkujący kod pisze zły kod.
Voo,
1
@immibis Nie wspomniałem Mapani 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 API Optional- nie jestem pewien, co to dowodzi. Dla Optionalniektórych tryGetmetod miałoby to jednak sens .
Voo,
2
@Voo Mapto przykład, który wszyscy znają. Oczywiście nie mogą uczynić Map.getzwrotu 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 ty theUserRepository.getUserDetails(loggedInUserID).get().
immibis
12

Kluczową kwestią dla mnie w korzystaniu z Opcjonalnego jest wyjaśnienie, kiedy programista musi sprawdzić, czy funkcja zwraca wartość, czy nie.

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());
Rodrigo Menezes
źródło
8
Zaskakuje mnie to, że wszystkie fajne funkcjonalne rzeczy z opcjami, choć naprawdę fajne, są tutaj uważane za ważniejsze niż ten punkt. Przed java 8 trzeba było przekopać Javadoc, aby dowiedzieć się, czy trzeba zerować odpowiedź z połączenia. Po napisaniu Java 8 wiesz, z typu zwrotu, czy możesz odzyskać „nic” z powrotem, czy nie.
Buhb
3
Cóż, istnieje problem „starego kodu”, w którym rzeczy wciąż mogą zwracać wartość null ... ale tak, możliwość odróżnienia od podpisu jest świetna.
Haakon Løtveit
5
Tak, z pewnością działa lepiej w językach, w których opcjonalny <T> jest jedynym sposobem na wyrażenie, że może brakować wartości, tj. W językach bez zerowego ptr.
dureuill,
8

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:

  • Ułatwia przejście starszego kodu w celu korzystania z nowych funkcji.
  • Ułatwia krzywe uczenia Opcjonalne.

Pierwszy jest raczej prosty.

Wyobraź sobie, że masz interfejs API, który wyglądał tak:

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

I miałeś klasę klasyczną, używając tego jako takiego (wypełnij puste pola):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

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ę:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

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:

  • Wywołujesz metodę, która może zwrócić null. Prawidłowym pomysłem byłoby to, że metoda zwraca null.
  • Używasz starszych metod bandaid, aby poradzić sobie z tym opcjonalnym, zamiast używać smacznych nowych metod, które zawierają wściekłość lambda.

Jeśli chcesz użyć Opcjonalnego jako prostej kontroli bezpieczeństwa zerowego, powinieneś to zrobić po prostu:

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

Oczywiście dobrze wyglądająca wersja tego wygląda następująco:

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

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.

Haakon Løtveit
źródło
1
Problem polega na tym, że starasz się, aby brzmiało to tak, jakby te funkcjonalne wersje były tak samo czytelne jak starsze wersje, w jednym przypadku nawet powiedzenie przykładu było „idealnie jasne”. Tak nie jest. Ten przykład był jasny, ale niezupełnie taki, ponieważ wymaga więcej przemyśleń. map(x).ifPresent(f)po prostu nie ma tego samego intuicyjnego sensu, co if(o != null) f(x)ma. ifWersja jest zupełnie jasne. To kolejny przypadek ludzi skaczących na popularny wagon-band, * nie * przypadek prawdziwego postępu.
Aaron,
1
Zauważ, że nie twierdzę, że korzystanie z programowania funkcjonalnego lub programu jest zerowe 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.
Aaron,
1
To zależy od naszych pojęć jasności. Podczas gdy robisz bardzo dobre uwagi, chciałbym również zauważyć, że dla wielu ludzi lambdas były w pewnym momencie nowe i dziwne. Odważyłbym się postawić internetowy plik cookie, który wiele osób twierdzi, że jest „Present” i odczuł cudowną ulgę: mogliby teraz zaktualizować starszy kod, a później nauczyć się składni lambda. Z drugiej strony osoby z Lispem, Haskellem lub podobnym językiem były prawdopodobnie zachwycone, że mogą teraz zastosować znane wzorce do swoich problemów w java ...
Haakon Løtveit
@Aaron - przejrzystość nie jest celem typów opcjonalnych. Osobiście uważam, że nie zmniejszają jasności, i są pewne przypadki (choć nie w tym przypadku), w których to zwiększają, ale kluczową korzyścią jest to, że (o ile unikasz używania isPresenti getna 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 jest Optional<Whatever>raczej niż null Whatever.
Jules
Nawet jeśli natychmiast usuniesz wartości, o ile ich nie użyjeszget , będziesz zmuszony wybrać, co zrobić, gdy pojawi się brakująca wartość: użyjesz orElse()(lub orElseGet()), aby podać wartość domyślną, lub orElseThrow()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.
Jules
0

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ę:

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

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.

użytkownik470365
źródło
12
Opcjonalne staje się bezcelową komplikacją w momencie użycia isPresent(). Używamy opcjonalnie, więc nie musimy testować.
candied_orange
2
Jak powiedzieli inni, nie używaj isPresent(). To niewłaściwe podejście do opcji. Użyj maplub flatMapzamiast.
Andres F.,
1
@CandiedOrange A jednak najczęściej głosowana odpowiedź (mówiąca o użyciu ifPresent) to tylko kolejny sposób na przetestowanie.
immibis
1
@CandiedOrange ifPresent(x)to tak samo test jak if(present) {x}. Zwracana wartość nie ma znaczenia.
immibis
1
@CandiedOrange Niemniej jednak pierwotnie powiedziałeś „więc nie musimy testować”. Nie możesz powiedzieć „żebyśmy nie musieli testować”, kiedy faktycznie testujesz… kim jesteś.
Aaron,
0

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.

Sanki
źródło
1
Im prostsza droga do osiągnięcia tego celu używa @Nullablei @ReturnValuesAreNonnullByDefaultwraz z analizą statyczną.
maaartinus
@maaartinus Dodanie dodatkowego oprzyrządowania w postaci analizy statycznej i dokumentacji w adnotacjach dekoratora jest prostsze niż obsługa API czasu kompilacji?
Sled
Dodanie dodatkowego oprzyrządowania rzeczywiście jest nieco prostsze, ponieważ nie wymaga żadnych zmian w kodzie. Co więcej, i tak chcesz mieć, ponieważ wyłapuje także inne błędy. +++ pisałem trywialny skrypt dodając package-info.javaze @ReturnValuesAreNonnullByDefaultwszystkich 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.
maaartinus
Moim największym zmartwieniem Optionaljest 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ż, że List<@Nullable String>wciąż jest to lista ciągów, a List<Optional<String>>nie jest.
maaartinus
0

Moja odpowiedź brzmi: po prostu nie. Pomyśl przynajmniej dwa razy, czy to naprawdę poprawa.

Wyrażenie (wzięte z innej odpowiedzi) jak

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

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

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

co czyni oczywistym, że mogą istnieć elseklauzule.

Funkcjonalny styl

  • wygląda zupełnie inaczej (i jest nieczytelny dla osób nieprzyzwyczajonych do tego)
  • to debugowanie
  • nie może być łatwo przedłużony do obsługi „nieobecnego” przypadku ( orElsenie zawsze jest wystarczający)
  • wprowadza w błąd, ignorując przypadek „nieobecny”

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

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

dobrze znany z innych języków. Czy zgadzamy się, że to oświadczenie

  • nie jest (naprawdę) funkcjonalny?
  • jest bardzo podobny do kodu w starym stylu, o wiele prostszy?
  • jest znacznie bardziej czytelny niż styl funkcjonalny?
  • jest przyjazny dla debuggera?
maaartinus
źródło
Wydaje się, że opisujesz implementację tej koncepcji przez C # z funkcją języka jako zupełnie inną niż implementacja Java z podstawową klasą biblioteki. Wyglądają tak samo dla mnie
Caleth
@Caleth Nie ma mowy. Moglibyśmy mówić o „koncepcji uproszczenia oceny zerowej”, ale nie nazwałbym tego „koncepcją”. Można powiedzieć, że Java implementuje to samo za pomocą klasy biblioteki podstawowej, ale to tylko bałagan. Po prostu zacznij od 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 raz Optional. 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.
maaartinus
Biblioteka klas: 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” to Optionalaka NullableakaMaybe
Caleth
Nie rozumiem, dlaczego uważasz, że jedną z nich jest „brzydkie znęcanie się”, a drugą dobrą. Rozumiem, że myślisz zarówno o „brzydkim nadużyciu”, jak i o obu dobrych.
Caleth
@ Caleth Jedną różnicą jest to, że zamiast dodawać dwa znaki zapytania, musisz przepisać wszystko. Problemem nie jest to, że funkcja Język i kody klas Bibliotek bardzo się różnią. W porządku. Problem polega na tym, że oryginalny kod i kody klas bibliotecznych bardzo się różnią. Ten sam pomysł, inny kod. Szkoda +++Nadużywane są lamdy do radzenia sobie z zerowalnością. Lamdy są w porządku, ale Optionalnie są. Nullability potrzebuje po prostu odpowiedniego wsparcia językowego, jak w Kotlin. Kolejny problem: List<Optional<String>>nie ma związku z tym List<String>, gdzie powinniśmy być powiązani.
maaartinus
0

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.

Petras Purlys
źródło