Wie sollen wir den jdk8-Stream für Nullwerte verwalten?

86

Hallo Java-Entwickler,

Ich weiß, dass das Thema ein bisschen sein kann, in advanceda das JDK8 noch nicht veröffentlicht ist (und vorerst sowieso nicht ..), aber ich habe einige Artikel über die Lambda-Ausdrücke und insbesondere den Teil gelesen, der sich auf die neue Sammlungs-API namens Stream bezieht.

Hier ist das Beispiel aus dem Artikel des Java-Magazins (es ist ein Otterpopulationsalgorithmus ..):

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());

Meine Frage ist, was passiert, wenn in der Mitte der internen Iteration von Set einer der Otter null ist?

Ich würde erwarten, dass eine NullPointerException ausgelöst wird, aber vielleicht stecke ich immer noch im vorherigen Entwicklungsparadigma (nicht funktionsfähig) fest. Kann mich jemand aufklären, wie damit umgegangen werden soll?

Wenn dies wirklich eine NullPointerException auslöst, finde ich die Funktion ziemlich gefährlich und muss nur wie folgt verwendet werden:

  • Entwickler, um sicherzustellen, dass es keinen Nullwert gibt (möglicherweise mit einem vorherigen .filter (o -> o! = Null))
  • Entwickler, um sicherzustellen, dass die Anwendung niemals einen Nullotter oder ein spezielles NullOtter-Objekt generiert, mit dem umgegangen werden soll.

Was ist die beste Option oder eine andere Option?

Vielen Dank!

clement
quelle
3
Ich würde sagen, es liegt am Programmierer, hier das Richtige zu tun. Die JVM und der Compiler können nur so viel. Beachten Sie, dass einige Sammlungsimplementierungen jedoch keine Nullwerte zulassen.
fge
18
Sie können filter(Objects::nonNull)mit Objectsvonjava.utils
Benj

Antworten:

44

Gegenwärtiges Denken scheint darin zu bestehen, Nullen zu "tolerieren", dh sie im Allgemeinen zuzulassen, obwohl einige Operationen weniger tolerant sind und möglicherweise NPE auslösen. Weitere Informationen zu dieser Nachricht finden Sie in der Diskussion der Nullen auf der Mailingliste der Lambda Libraries-Expertengruppe . In der Folge ergab sich ein Konsens über Option 3 (mit einem bemerkenswerten Einwand von Doug Lea). Ja, die Besorgnis des OP über mit NPE explodierende Pipelines ist berechtigt.

Nicht umsonst bezeichnete Tony Hoare Nullen als "Milliarden-Dollar-Fehler". Der Umgang mit Nullen ist ein wahrer Schmerz. Selbst bei klassischen Sammlungen (ohne Berücksichtigung von Lambdas oder Streams) sind Nullen problematisch. Wie fge in einem Kommentar erwähnt, erlauben einige Sammlungen nulls und andere nicht. Bei Sammlungen, die Nullen zulassen, führt dies zu Mehrdeutigkeiten in der API. Bei Map.get () gibt eine Null-Rückgabe beispielsweise an, dass der Schlüssel vorhanden und sein Wert null ist oder dass der Schlüssel fehlt. Man muss zusätzliche Arbeit leisten, um diese Fälle zu disambiguieren.

Die übliche Verwendung für null ist das Fehlen eines Wertes. Der für Java SE 8 vorgeschlagene Ansatz besteht darin, einen neuen java.util.OptionalTyp einzuführen , der das Vorhandensein / Fehlen eines Werts zusammen mit dem Verhalten der Angabe eines Standardwerts oder des Auslösens einer Ausnahme oder des Aufrufs einer Funktion usw. kapselt Der Wert fehlt. Optionalwird jedoch nur von neuen APIs verwendet, alles andere im System muss sich jedoch mit der Möglichkeit von Nullen abfinden.

Mein Rat ist, tatsächliche Nullreferenzen so weit wie möglich zu vermeiden. Aus dem Beispiel ist schwer zu ersehen, wie es einen "Null" Otter geben könnte. Wenn dies jedoch erforderlich wäre, sind die Vorschläge des OP, Nullwerte herauszufiltern oder sie einem Sentinel-Objekt (dem Null-Objektmuster ) zuzuordnen, gute Ansätze.

Stuart Marks
quelle
3
Warum Nullen vermeiden? Sie werden häufig in Datenbanken verwendet.
Raffi Khatchadourian
4
@RaffiKhatchadourian Ja, Nullen werden in Datenbanken verwendet, sind aber ebenso problematisch. Sehen Sie dies und das und lesen Sie alle Antworten und Kommentare. Berücksichtigen Sie auch die Auswirkungen von SQL null auf boolesche Ausdrücke: en.wikipedia.org/wiki/… ... dies ist eine reichhaltige Quelle für Abfragefehler.
Stuart Marks
@ RaffiKhatchadourian Weil scheiße nullist, deshalb. Acc. Laut einer Umfrage, die ich gesehen habe (ich kann sie nicht finden), ist NPE die Ausnahme Nummer 1 in Java. SQL ist keine Programmiersprache auf hoher Ebene, sicherlich nicht funktionsfähig, was null verachtet, also ist es ihm egal.
Abhijit Sarkar
88

Obwohl die Antworten zu 100% korrekt sind, ein kleiner Vorschlag zur Verbesserung der nullFallbehandlung der Liste selbst mit Optional :

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

Mit diesem Teil Optional.ofNullable(listOfStuff).orElseGet(Collections::emptyList)können Sie den Fall, wenn er listOfStuffnull ist, gut behandeln und eine leere Liste zurückgeben, anstatt mit NullPointerException zu scheitern.

Johnny
quelle
2
Ich mag das, vermeidet es explizit nach Null zu suchen.
Chris
1
das sieht am besten klar aus .. schön und genau das, was ich brauchte
Abdullah Al Noman
Es wird explizit nur auf andere Weise nach Null gesucht. Wenn es null sein darf, sollte es optional sein, das ist die Idee, oder? Verbieten Sie alle Nullwerte mit Optionen. Darüber hinaus ist es eine schlechte Praxis, anstelle einer leeren Liste null zurückzugeben, sodass zwei Code-Gerüche angezeigt werden, wenn ich dies sehe: Es werden keine Optionals verwendet und keine leeren Streams zurückgegeben. Und letzteres ist seit über 20 Jahren erhältlich, das ist also ausgereift ...
Koos Gadellaa
Was ist, wenn ich eine leere Liste zurückgeben möchte?
Ashburn RK
@ AshburnRK es ist eine schlechte Praxis. Sie sollten eine leere Liste zurückgeben.
Johnny
68

Die Antwort von Stuart liefert eine gute Erklärung, aber ich möchte ein anderes Beispiel geben.

Ich bin auf dieses Problem gestoßen, als ich versucht habe, einen reduceStream mit Nullwerten auszuführen (tatsächlich war LongStream.average()dies eine Art von Reduzierung). Da durchschnitt () zurückgibt OptionalDouble, habe ich angenommen, dass der Stream Nullen enthalten könnte, aber stattdessen wurde eine NullPointerException ausgelöst. Dies liegt an Stuarts Erklärung von null gegen leer.

Also, wie das OP vorschlägt, habe ich einen Filter wie folgt hinzugefügt:

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

Oder verwenden Sie, wie unten angegeben, das von der Java-API bereitgestellte Prädikat:

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

Aus der Mailinglistendiskussion hat Stuart verlinkt: Brian Goetz über Nullen in Streams

bparry
quelle
19

Wenn Sie nur Nullwerte aus einem Stream herausfiltern möchten, können Sie einfach einen Methodenverweis auf java.util.Objects.nonNull (Object) verwenden . Aus seiner Dokumentation:

Diese Methode kann als Prädikat verwendet werden.filter(Objects::nonNull)

Beispielsweise:

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

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

Dies wird gedruckt:

Foo
Bar
Andy Thomas
quelle
7

Ein Beispiel, wie Sie Null vermeiden können, z. B. Filter vor groupingBy verwenden

Filtern Sie die Nullinstanzen vor groupingBy heraus.

Hier ist ein Beispiel

MyObjectlist.stream()
            .filter(p -> p.getSomeInstance() != null)
            .collect(Collectors.groupingBy(MyObject::getSomeInstance));
Ilan M.
quelle