Czy gettery Java 8 powinny zwracać opcjonalny Typ?

Optional Typ wprowadzony w Javie 8 jest nowością dla wielu programistów.

Czy metoda gettera zwracająca typ Optional<Foo> zamiast klasycznego Foo jest dobrą praktyką? Załóżmy, że wartość może być null.

Author: Nayuki, 2014-10-12

5 answers

Oczywiście, ludzie będą robić, co chcą. Ale mieliśmy wyraźną intencję przy dodawaniu tej funkcji i było to Nie być może typem ogólnego przeznaczenia, tak jak wiele osób by tego chciało. Naszym zamiarem było zapewnienie ograniczonego mechanizmu dla typów zwracających metody biblioteczne, w których musiał istnieć jasny sposób reprezentowania "brak wyniku", a użycie null W takim przypadku było w przeważającej mierze prawdopodobne, że spowoduje błędy.

Na przykład, prawdopodobnie nigdy nie powinieneś go używać dla czegoś, co zwraca tablicę wyników lub listę wyników; zamiast tego zwraca pustą tablicę lub listę. Prawie nigdy nie powinieneś używać go jako pola czegoś lub parametru metody.

Myślę, że rutynowe używanie go jako wartości zwrotnej dla getterów byłoby zdecydowanie nadmiernym wykorzystaniem.

Nie ma nic } złego z opcjonalnym, że należy go unikać, to po prostu nie jest to, co wielu ludzi życzyłoby sobie, a zatem byliśmy dość zaniepokojeni ryzykiem gorliwych nadmierne zużycie.

(Public service announcement: NEVER call Optional.get unless you can prove it will never be null; zamiast tego użyj jednej z bezpiecznych metod, takich jak orElse lub ifPresent. Z perspektywy czasu powinniśmy byli nazwać get czymś w rodzaju getOrElseThrowNoSuchElementException lub czymś, co znacznie wyjaśniło, że jest to wysoce niebezpieczna metoda, która podważa cały cel Optional. Mam lekcję. (UPDATE: Java 10 mA Optional.orElseThrow(), co jest semantycznie równoważne get(), ale którego nazwa jest bardziej odpowiednie.))

 358
Author: Brian Goetz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-12 12:23:55

Po przeprowadzeniu własnych badań natknąłem się na wiele rzeczy, które mogą sugerować, kiedy jest to właściwe. Najbardziej autorytatywny jest następujący cytat z artykułu Oracle:

"Ważne jest, aby zauważyć, że intencją klasy opcjonalnej jest nie zastępowanie każdego pojedynczego odniesienia null . Zamiast tego, jej celem jest pomoc w projektowaniu bardziej zrozumiałych interfejsów API , aby po prostu odczytaniu podpisu metody można było stwierdzić, czy można oczekiwać opcjonalnej wartości. Zmusza to do aktywnego rozpakowania opcji, aby poradzić sobie z brakiem wartości." - masz dość wyjątków od wskaźnika Null? Rozważ użycie opcjonalnej Javy SE 8!

Znalazłem również ten fragment z Java 8 Opcjonalnie: jak go używać

"opcjonalne nie powinno być używane w tych kontekstach, ponieważ nic nam nie kupi:

  • w warstwie modelu domeny (bez serializacji)
  • w DTOs (ten sam powód)
  • w parametrach wejściowych metod
  • w parametrach konstruktora "

Co również wydaje się podnosić pewne ważne punkty.

Nie byłem w stanie znaleźć żadnych negatywnych konotacji lub czerwonych flag sugerujących, że Optional Należy unikać. Myślę, że ogólna idea jest taka, że jeśli jest to pomocne lub poprawia użyteczność twojego API, użyj go.

 59
Author: Justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-04 20:20:28

Powiedziałbym ogólnie, że dobrym pomysłem jest użycie opcjonalnego typu dla zwracanych wartości, które mogą być nullable. Jednak w.r.t. do frameworków zakładam, że zastąpienie klasycznych getterów typami opcjonalnymi spowoduje wiele kłopotów podczas pracy z frameworkami (np. Hibernate), które opierają się na konwencjach kodowania dla getterów i setterów.

 12
Author: Claas Wilke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-12 17:43:36

Powodem dodania Optional do Javy jest to, że:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

Jest czystsze niż to:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Chodzi mi o to, że opcjonalny został napisany do obsługi programowania funkcyjnego , który został dodany do Javy w tym samym czasie. (Przykład pochodzi dzięki uprzejmości blogu Briana Goetza. Lepszy przykład może użyć metody orElse(), ponieważ ten kod i tak wyrzuci wyjątek, ale masz obrazek.)

Ale teraz ludzie używają opcjonalnego dla zupełnie innego powód. Używają go do usunięcia wady konstrukcji języka. Wadą jest to, że nie można określić, które z parametrów API i wartości zwracanych mogą być null. Można o tym wspomnieć w Javadoc, ale większość programistów nawet nie pisze Javadoc dla swojego kodu i niewielu będzie sprawdzać Javadoc podczas pisania. Prowadzi to więc do wielu kodów, które zawsze sprawdzają wartości null przed ich użyciem, nawet jeśli często nie mogą być null, ponieważ były już sprawdzane wielokrotnie dziewięć lub dziesięć razy w górę stosu połączeń.

Myślę, że było prawdziwe pragnienie, aby rozwiązać tę wadę, ponieważ tak wielu ludzi, którzy widzieli nową klasę opcjonalną, założyło, że jej celem jest dodanie jasności do interfejsów API. Dlatego ludzie zadają pytania w stylu " czy getters powinien zwrócić Optionals?"Nie, prawdopodobnie nie powinny, chyba że spodziewasz się, że getter zostanie użyty w programowaniu funkcyjnym, co jest bardzo mało prawdopodobne. W rzeczywistości, jeśli spojrzeć na to, gdzie opcjonalne jest używane w Java API, to głównie w klasy Stream, które są podstawą programowania funkcyjnego. (Nie sprawdzałem zbyt dokładnie, ale Klasy Stream mogą być tylko miejsce, w którym są używane.)

Jeśli planujesz użyć gettera w odrobinie funkcjonalnego kodu, dobrym pomysłem może być posiadanie standardowego gettera i drugiego, który zwraca opcjonalny.

Jeśli chcesz, żeby twoja klasa była serializowana, absolutnie nie powinieneś używać opcji.

Opcje są bardzo złym rozwiązaniem dla API wada, ponieważ a) są bardzo gadatliwe, i b) nigdy nie były przeznaczone do rozwiązania tego problemu w pierwszej kolejności.

Znacznie lepszym rozwiązaniem wada API jest Nullness Checker. Jest to procesor adnotacji, który pozwala określić, które parametry i wartości zwracane mogą być null, dodając do nich adnotację @Nullable. W ten sposób kompilator może zeskanować kod i dowiedzieć się, czy wartość, która może być null, jest przekazywana do wartości, w której null nie jest dozwolone. Przez domyślnie zakłada, że nic nie może być null, chyba że jest tak adnotowane. W ten sposób nie musisz się martwić o wartości null. Przekazanie wartości null do parametru spowoduje błąd kompilatora. Testowanie obiektu pod kątem null, który nie może być null, powoduje Ostrzeżenie kompilatora. Efektem tego jest zmiana NullPointerException z błędu runtime na błąd kompilacji.

To wszystko zmienia. Jeśli chodzi o getery, nie używaj opcji. I postaraj się zaprojektować swoje klasy tak, aby nie członków może być null. I może spróbuj dodać Nullness Checker do swojego projektu i zadeklarować swoje parametry getter i setter @ Nullable, jeśli tego potrzebują. Robiłem to tylko przy nowych projektach. Prawdopodobnie generuje wiele ostrzeżeń w istniejących projektach napisanych z dużą ilością zbędnych testów dla null, więc może być trudne do modernizacji. Ale będzie również złapać wiele błędów. Podoba mi się. Mój kod jest znacznie czystszy i bardziej niezawodny z tego powodu.

(jest też nowy język, który się tym zajmuje. Kotlin, który kompiluje się do kodu bajtowego Javy, pozwala określić, czy obiekt może być null podczas deklarowania go. To czystsze podejście.)

Dodatek do oryginalnego postu (Wersja 2)

Po długim namyśle, niechętnie doszedłem do wniosku, że jest dopuszczalne zwracanie opcjonalne pod jednym warunkiem: że pobrana wartość może być w rzeczywistości null. Widziałem wiele kodów, w których ludzie rutynowo zwracają opcjonalnie z getterów to nie może zwrócić null. Postrzegam to jako bardzo złą praktykę kodowania, która tylko zwiększa złożoność kodu, co zwiększa prawdopodobieństwo błędów. Ale gdy zwracana wartość może być rzeczywiście null, śmiało i owinąć ją wewnątrz opcjonalnego.

Należy pamiętać, że metody, które są przeznaczone do programowania funkcyjnego i które wymagają odniesienia do funkcji, będą (i powinny) być napisane w dwóch formach, z których jedna wykorzystuje opcjonalne. Na przykład Optional.map() i Optional.flatMap() przyjmują odwołania do funkcji. Pierwszy bierze odwołanie do zwykłego gettera, a drugi bierze jeden, który zwraca opcjonalny. Więc nie wyświadczasz nikomu przysługi, zwracając opcję, w której wartość nie może być null.

Mimo wszystko, nadal widzę, że podejście używane przez sprawdzanie Nullness jest najlepszym sposobem radzenia sobie z NULL, ponieważ zamieniają One NullPointerExceptions z błędów runtime na błędy kompilacji.

 4
Author: MiguelMunoz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-06 03:29:46

Jeśli używasz nowoczesnych serializerów i innych frameworków, które rozumieją Optional, to odkryłem, że te wytyczne działają dobrze podczas pisaniaEntity fasoli i warstw domen: {]}

  1. jeśli warstwa serializacyjna (Zwykle DB) dopuszcza wartość null dla komórki w kolumnie BAR w tabeli FOO, to getter Foo.getBar() może zwrócić Optional wskazując wywoływaczowi, że można oczekiwać, że wartość ta będzie null i powinien się tym zająć. Jeżeli DB gwarantuje wartość nie będzie null wtedy getter powinien Nie zawijać to w Optional.
  2. Foo.bar powinno być private i a nie być Optional. Naprawdę nie ma powodu, aby to było Optional, jeśli jest private.
  3. seter Foo.setBar(String bar) powinien przyjmować Typ bar i Nie Optional. Jeśli można użyć argumentu null, należy to podać w komentarzu JavaDoc. Jeśli nie jest w porządku używać null an IllegalArgumentException lub jakaś odpowiednia logika biznesowa jest, IMHO, bardziej odpowiednia.
  4. konstruktory nie potrzebują Optional argumentów(z powodów podobnych do punktu 3). Generalnie w konstruktorze umieszczam tylko argumenty, które muszą być nie-null w bazie danych serializacji.

Aby powyższe rozwiązanie było bardziej wydajne, możesz edytować szablony IDE do generowania getterów i odpowiadających im szablonów dla toString(), equals(Obj o) itd. lub używać pól bezpośrednio dla nich (większość generatorów IDE ma już do czynienia z NULL).

 2
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-30 19:20:32