Jak powinniśmy zarządzać strumieniem jdk8 dla wartości null

88

Witajcie, drodzy programiści Java,

Wiem, że temat może być trochę, in advanceponieważ JDK8 nie jest jeszcze wydany (a przynajmniej na razie nie…), ale czytałem kilka artykułów na temat wyrażeń Lambda, a zwłaszcza części związanej z nowym API kolekcji znanym jako Stream.

Oto przykład podany w artykule Java Magazine (jest to algorytm populacji wydry ..):

Set<Otter> otters = getOtters();
System.out.println(otters.stream()
    .filter(o -> !o.isWild())
    .map(o -> o.getKeeper())
    .filter(k -> k.isFemale())
    .into(new ArrayList<>())
    .size());

Moje pytanie brzmi: co się stanie, jeśli w środku wewnętrznej iteracji Seta jedna z wydr jest zerowa?

Spodziewałbym się, że zostanie wyrzucony wyjątek NullPointerException, ale może nadal tkwię w poprzednim paradygmacie programowania (niefunkcjonalnym), czy ktoś może mnie oświecić, jak należy sobie z tym poradzić?

Jeśli to naprawdę wyrzuci wyjątek NullPointerException, uważam tę funkcję za dość niebezpieczną i będzie musiała być używana tylko jak poniżej:

  • Deweloper, aby upewnić się, że nie ma wartości null (może przy użyciu poprzedniego .filter (o -> o! = Null))
  • Deweloper, aby upewnić się, że aplikacja nigdy nie generuje pustej wydry ani specjalnego obiektu NullOtter do obsługi.

Jaka jest najlepsza opcja, czy jakakolwiek inna opcja?

Dzięki!

łaskawy
źródło
3
Powiedziałbym, że to od programisty zależy, czy postąpi właściwie; JVM i kompilator mogą zrobić tylko tyle. Należy jednak pamiętać, że niektóre implementacje kolekcji nie zezwalają na wartości null.
fge
18
Można korzystać filter(Objects::nonNull)z Objectsodjava.utils
Benj

Odpowiedzi:

45

Obecne myślenie wydaje się polegać na „tolerowaniu” wartości zerowych, to znaczy ogólnie na dopuszczaniu ich, chociaż niektóre operacje są mniej tolerancyjne i mogą w końcu spowodować wyrzucenie NPE. Zobacz dyskusję nulls na liście mailingowej grupy ekspertów Lambda Libraries, a konkretnie tę wiadomość . Następnie wyłonił się konsensus co do opcji nr 3 (z wyraźnym sprzeciwem ze strony Douga Lei). A więc tak, obawy PO dotyczące wybuchu rurociągów z NPE są uzasadnione.

Nie bez powodu Tony Hoare nazywał wartości zerowe „błędem za miliard dolarów”. Radzenie sobie z wartościami zerowymi to prawdziwy ból. Nawet w przypadku klasycznych kolekcji (bez uwzględnienia lambd lub strumieni) wartości null są problematyczne. Jak wspomniał fge w komentarzu, niektóre kolekcje dopuszczają wartości null, a inne nie. W przypadku kolekcji, które zezwalają na wartości null, wprowadza to niejednoznaczności do interfejsu API. Na przykład w przypadku Map.get () zwrot null wskazuje, że klucz jest obecny i jego wartość wynosi null, lub że klucz jest nieobecny. Trzeba wykonać dodatkową pracę, aby ujednoznacznić te przypadki.

Typowym zastosowaniem wartości null jest oznaczenie braku wartości. Podejście do radzenia sobie z tym zaproponowane dla Java SE 8 polega na wprowadzeniu nowego java.util.Optionaltypu, który hermetyzuje obecność / brak wartości, wraz z zachowaniami polegającymi na dostarczaniu wartości domyślnej lub rzucaniu wyjątku lub wywoływaniu funkcji itp. wartość jest nieobecna. Optionaljest używany tylko przez nowe API, jednak wszystko inne w systemie wciąż musi znosić możliwość zerowania.

Radzę unikać rzeczywistych odniesień zerowych w największym możliwym stopniu. Z podanego przykładu trudno wywnioskować, jak może istnieć „zerowa” Wydra. Ale gdyby było to konieczne, sugestie OP dotyczące odfiltrowywania wartości zerowych lub mapowania ich na obiekt wartowniczy ( wzorzec obiektu zerowego ) są dobrym podejściem.

Stuart Marks
źródło
3
Po co unikać wartości zerowych? Są szeroko stosowane w bazach danych.
Raffi Khatchadourian
5
@RaffiKhatchadourian Tak, wartości null są używane w bazach danych, ale są równie problematyczne. Zobacz to i to i przeczytaj wszystkie odpowiedzi i komentarze. Weź również pod uwagę wpływ SQL null na wyrażenia boolowskie: en.wikipedia.org/wiki/ ... to jest bogate źródło błędów zapytań.
Stuart Marks
1
@RaffiKhatchadourian Ponieważ nulljest do bani, właśnie dlatego. Acc. do ankiety, którą widziałem (nie mogę jej znaleźć), NPE to wyjątek numer 1 w Javie. SQL nie jest językiem programowania wysokiego poziomu, a na pewno nie jest funkcjonalny, który gardzi wartością zerową, więc go to nie obchodzi.
Abhijit Sarkar
91

Chociaż odpowiedzi są w 100% poprawne, mała sugestia, aby poprawić nullobsługę przypadku samej listy za pomocą opcji Opcjonalne :

 List<String> listOfStuffFiltered = Optional.ofNullable(listOfStuff)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

Ta część Optional.ofNullable(listOfStuff).orElseGet(Collections::emptyList)pozwoli ci ładnie obsłużyć przypadek, gdy listOfStuffjest null i zwrócić emptyList zamiast niepowodzenia z NullPointerException.

Jasio
źródło
2
Podoba mi się to, unika jawnego sprawdzania null.
Chris,
1
to wygląda najlepiej jasno ... ładnie i dokładnie to, czego potrzebowałem
Abdullah Al Noman
Jawnie sprawdza wartość null, tylko w inny sposób. Jeśli dozwolone jest być zerowe, powinno to być opcjonalne, taki jest pomysł, prawda? Banuj wszystkie wartości null za pomocą opcji. Co więcej, zwracanie wartości null zamiast pustej listy jest złą praktyką, więc jeśli widzę to, pokazuje dwa zapachy kodu: nie są używane opcje opcjonalne i nie są zwracane puste strumienie. A ten ostatni jest dostępny od ponad 20 lat, więc to już dojrzałe ...
Koos Gadellaa
co jeśli chcę zwrócić pustą listę zamiast wartości null?
Ashburn RK
@AshburnRK to zła praktyka. Powinieneś zwrócić pustą listę.
Johnny
69

Odpowiedź Stuarta stanowi świetne wyjaśnienie, ale chciałbym podać inny przykład.

Napotkałem ten problem podczas próby wykonania a reducena strumieniu zawierającym wartości null (w rzeczywistości tak było LongStream.average(), co jest rodzajem redukcji). Ponieważ średnia () zwraca OptionalDouble, założyłem, że Stream może zawierać wartości null, ale zamiast tego został zgłoszony wyjątek NullPointerException. Wynika to z wyjaśnienia Stuarta, że null v. Empty.

Tak więc, jak sugeruje OP, dodałem taki filtr:

list.stream()
    .filter(o -> o != null)
    .reduce(..);

Lub, jak wskazano poniżej, użyj predykatu dostarczonego przez Java API:

list.stream()
    .filter(Objects::nonNull)
    .reduce(..);

Z dyskusji na liście mailingowej podlinkował Stuart: Brian Goetz o null w strumieniach

bparry
źródło
19

Jeśli chcesz tylko odfiltrować wartości null ze strumienia, możesz po prostu użyć odwołania do metody do java.util.Objects.nonNull (Object) . Z jego dokumentacji:

Ta metoda istnieje do wykorzystania jako orzecznik ,filter(Objects::nonNull)

Na przykład:

List<String> list = Arrays.asList( null, "Foo", null, "Bar", null, null);

list.stream()
    .filter( Objects::nonNull )  // <-- Filter out null values
    .forEach( System.out::println );

Spowoduje to wydrukowanie:

Foo
Bar
Andy Thomas
źródło
7

Przykład unikania null, np. Użyj filtra przed groupingBy

Odfiltruj wystąpienia null przed groupingBy.

Oto przykład

MyObjectlist.stream()
            .filter(p -> p.getSomeInstance() != null)
            .collect(Collectors.groupingBy(MyObject::getSomeInstance));
Ilan M
źródło