Mehrere Nullprüfungen in Java 8

99

Ich habe den folgenden Code, der für mehrere Nullprüfungen etwas hässlich ist.

String s = null;

if (str1 != null) {
    s = str1;
} else if (str2 != null) {
    s = str2;
} else if (str3 != null) {
    s = str3;
} else {
    s = str4;
}

Also habe ich versucht, Optional.ofNullablewie unten zu verwenden, aber es ist immer noch schwer zu verstehen, wenn jemand meinen Code liest. Was ist der beste Ansatz, um dies in Java 8 zu tun?

String s = Optional.ofNullable(str1)
                   .orElse(Optional.ofNullable(str2)
                                   .orElse(Optional.ofNullable(str3)
                                                   .orElse(str4)));

In Java 9 können wir Optional.ofNullablemit verwenden OR, aber in Java8 gibt es einen anderen Ansatz?

Funken
quelle
4
Die Java9- orSyntax String s = Optional.ofNullable(str1) .or(() -> Optional.ofNullable(str2)) .or(() -> Optional.ofNullable(str3)) .orElse(str4);sieht nicht so gut aus wie die, die Stream.ofich sya würde.
Naman
2
@ OleV.V. Nichts falsches, das OP ist sich dessen bereits bewusst und sucht etwas Spezielles für Java-8.
Naman
3
Ich weiß, dass der Benutzer nach einer Java-8-spezifischen Lösung fragt, aber allgemein würde ich mitStringUtils.firstNonBlank()
Mohamed Anees A
3
Das Problem ist, dass Java 8 / Streams nicht die beste Lösung dafür ist. Dieser Code riecht wirklich nach einem Refactor, aber ohne mehr Kontext ist es wirklich schwer zu sagen. Für den Anfang - warum sind nicht drei Objekte, die wahrscheinlich so eng miteinander verbunden sind, noch nicht in einer Sammlung?
Bill K
2
@MohamedAneesA hätte die beste Antwort (als Kommentar) geliefert, aber in diesem Fall die Quelle von StringUtils nicht angegeben. Wenn Sie diese als eine Reihe separater Zeichenfolgen haben MÜSSEN, ist es auf jeden Fall ideal, sie als vargs-Methode wie "firstNonBlank" zu codieren. Die darin enthaltene Syntax ist ein Array, das für jede Schleife eine einfache Schleife mit einer Rückkehr zum Finden von a erstellt Nicht-Null-Wert trivial und offensichtlich. In diesem Fall sind Java 8-Streams ein attraktives Ärgernis. Sie verleiten Sie dazu, etwas zu inline und zu komplizieren, das eine einfache Methode / Schleife sein sollte.
Bill K

Antworten:

172

Sie können es so machen:

String s = Stream.of(str1, str2, str3)
    .filter(Objects::nonNull)
    .findFirst()
    .orElse(str4);
Ravindra Ranwala
quelle
16
Dies. Denken Sie an das, was Sie brauchen, nicht an das, was Sie haben.
Thorbjørn Ravn Andersen
21
Wie hoch ist die Geschwindigkeit? Das Erstellen von Stream-Objekten, das Aufrufen von 4 Methoden und das Erstellen temporärer Arrays ( {str1, str2, str3}) sehen viel langsamer aus als ein lokales ifoder ?:, das die Java-Laufzeit optimieren kann. Gibt es eine Stream-spezifische Optimierung in javacund die Java-Laufzeit, die es so schnell macht wie ?:? Wenn nicht, empfehle ich diese Lösung nicht für leistungskritischen Code.
Punkte
16
@pts Dies ist sehr wahrscheinlich langsamer als ?:Code und ich bin sicher, dass Sie es in leistungskritischem Code vermeiden sollten. Es ist jedoch viel besser lesbar und Sie sollten es in nicht leistungskritischem Code IMO empfehlen, der sicher mehr als 99% des Codes ausmacht.
Aaron
7
@pts Es gibt keine Stream-spezifischen Optimierungen, weder in javacnoch zur Laufzeit. Dies schließt die allgemeinen Optimierungen wie das Inlinen des gesamten Codes und das anschließende Eliminieren redundanter Operationen nicht aus. Im Prinzip könnte das Endergebnis so effizient sein wie die einfachen bedingten Ausdrücke. Es ist jedoch eher unwahrscheinlich, dass es jemals dort ankommt, da die Laufzeit den erforderlichen Aufwand nur für die heißesten Codepfade aufwenden würde.
Holger
22
Tolle Lösung! Um die Lesbarkeit ein wenig zu verbessern, würde ich vorschlagen str4, die Parameter von Stream.of(...)und orElse(null)am Ende zu verwenden.
Danielp
73

Wie wäre es mit einem ternären bedingten Operator?

String s = 
    str1 != null ? str1 : 
    str2 != null ? str2 : 
    str3 != null ? str3 : str4
;
Eran
quelle
50
Eigentlich sucht er nach "dem besten Ansatz, dies in Java8 zu tun". Dieser Ansatz kann in Java8 verwendet werden, daher hängt alles nur davon ab, was das OP unter "dem Besten" versteht und auf welchen Gründen er die Entscheidung darüber stützt, was besser ist.
Stultuske
17
Normalerweise mag ich verschachtelte Ternäre nicht, aber das sieht ziemlich sauber aus.
JollyJoker
11
Dies ist ein großer Schmerz für jeden, der nicht jeden Tag verschachtelte ternäre Operatoren liest.
Cubic
2
@Cubic: Wenn du denkst, dass es schwer zu analysieren ist, schreibe einen Kommentar wie // take first non-null of str1..3. Sobald Sie wissen, was es tut, wird es leicht zu sehen, wie.
Peter Cordes
4
@Cubic Dies ist jedoch ein einfaches, sich wiederholendes Muster. Sobald Sie Zeile 2 analysiert haben, haben Sie die gesamte Sache analysiert, unabhängig davon, wie viele Fälle enthalten sind. Und wenn Sie fertig sind, haben Sie gelernt, eine ähnliche Auswahl von zehn verschiedenen Fällen kurz und relativ einfach auszudrücken. Wenn Sie das nächste Mal eine ?:Leiter sehen, wissen Sie, was sie tut. (Übrigens wissen funktionale Programmierer dies als (cond ...)Klauseln: Es ist immer eine Wache, gefolgt von dem entsprechenden Wert, der verwendet werden soll, wenn die Wache wahr ist.)
cmaster - Monica
35

Sie können auch eine Schleife verwenden:

String[] strings = {str1, str2, str3, str4};
for(String str : strings) {
    s = str;
    if(s != null) break;
}
ernest_k
quelle
27

Aktuelle Antworten sind nett, aber Sie sollten das wirklich in eine Utility-Methode einfügen:

public static Optional<String> firstNonNull(String... strings) {
    return Arrays.stream(strings)
            .filter(Objects::nonNull)
            .findFirst();
}

Diese Methode ist seit Jahren in meiner UtilKlasse und macht Code viel sauberer:

String s = firstNonNull(str1, str2, str3).orElse(str4);

Sie können es sogar generisch machen:

@SafeVarargs
public static <T> Optional<T> firstNonNull(T... objects) {
    return Arrays.stream(objects)
            .filter(Objects::nonNull)
            .findFirst();
}

// Use
Student student = firstNonNull(student1, student2, student3).orElseGet(Student::new);
walen
quelle
10
FWIW, in SQL heißt diese Funktion Coalesce , also nenne ich sie auch in meinem Code so. Ob das für Sie funktioniert, hängt davon ab, wie sehr Sie SQL wirklich mögen.
Tom Anderson
5
Wenn Sie es in eine Dienstprogrammmethode einfügen, können Sie es auch effizient gestalten.
Todd Sewell
1
@ToddSewell, was meinst du?
Gustavo Silva
5
@GustavoSilva Todd bedeutet wahrscheinlich, dass es keinen Sinn macht, zu verwenden, Arrays.stream()wenn ich dasselbe mit a forund a tun kann != null, was effizienter ist. Und Todd hätte recht. Als ich diese Methode codierte, suchte ich jedoch nach einer Möglichkeit, dies mithilfe von Java 8-Funktionen wie OP zu tun.
Wal
4
@GustavoSilva Ja, das ist es im Grunde: Wenn Sie dies tun wollen, ist dies eine nützliche Funktion. Bedenken hinsichtlich sauberem Code sind nicht mehr so ​​wichtig, und Sie können auch eine schnellere Version verwenden.
Todd Sewell
13

Ich benutze eine Hilfsfunktion, so etwas wie

T firstNonNull<T>(T v0, T... vs) {
  if(v0 != null)
    return v0;
  for(T x : vs) {
    if (x != null) 
      return x;
  }
  return null;
}

Dann kann diese Art von Code geschrieben werden als

String s = firstNonNull(str1, str2, str3, str4);
Michael Anderson
quelle
1
Warum der zusätzliche v0Parameter?
tobias_k
1
@tobias_k Der zusätzliche Parameter bei Verwendung von varargs ist die idiomatische Methode in Java, 1 oder mehr Argumente anstelle von 0 oder mehr zu erfordern. (Siehe Punkt 53 von Effective Java, Ed. 3., der minals Beispiel dient.) Ich bin weniger davon überzeugt, dass dies hier angemessen ist.
Nick
3
@Nick Ja, das habe ich mir gedacht, aber die Funktion würde ohne sie genauso gut funktionieren (tatsächlich genauso verhalten).
tobias_k
1
Einer der Hauptgründe für das Übergeben eines zusätzlichen ersten Arguments besteht darin, das Verhalten beim Übergeben eines Arrays explizit anzugeben. In diesem Fall möchte ich firstNonNull(arr)arr zurückgeben, wenn es nicht null ist. Wenn dies der firstNonNull(T... vs)Fall wäre, würde stattdessen der erste Nicht-Null-Eintrag in arr zurückgegeben.
Michael Anderson
4

Eine Lösung, die auf so viele Elemente angewendet werden kann, wie Sie möchten, kann sein:

Stream.of(str1, str2, str3, str4)
      .filter(Object::nonNull)
      .findFirst()
      .orElseThrow(IllegalArgumentException::new)

Sie können sich eine Lösung wie die folgende vorstellen, aber die erste sorgt non nullityfür alle Elemente

Stream.of(str1, str2, str3).....orElse(str4)
Azro
quelle
6
oder Else, str4obwohl es tatsächlich eine Null ist
Naman
1
@nullpointer Or orElseNull, was gleich ist, wenn str4null ist.
tobias_k
3

Sie können auch alle Strings in einem Array von Strings zusammenfassen und dann eine for-Schleife ausführen, um die Schleife zu überprüfen und zu verlassen, sobald sie zugewiesen ist. Angenommen, s1, s2, s3, s4 sind alle Strings.

String[] arrayOfStrings = {s1, s2, s3};


s = s4;

for (String value : arrayOfStrings) {
    if (value != null) { 
        s = value;
        break;
    }
}

Bearbeitet, um die Bedingung für die Standardeinstellung auf s4 zu setzen, wenn keine zugewiesen ist.

danielctw
quelle
Sie haben weggelassen s4(oder str4), dem sam Ende zugewiesen werden soll , auch wenn es null ist.
DisplayName
3

Methodenbasiert und einfach.

String getNonNull(String def, String ...strings) {
    for(int i=0; i<strings.length; i++)
        if(strings[i] != null)
             return s[i];
    return def;
}

Und benutze es als:

String s = getNonNull(str4, str1, str2, str3);

Es ist einfach mit Arrays zu tun und sieht hübsch aus.

Madhusoodan P.
quelle
0

Wenn Sie Apache Commons Lang 3 verwenden, kann es folgendermaßen geschrieben werden:

String s = ObjectUtils.firstNonNull(str1, str2, str3, str4);

Die Nutzung ObjectUtils.firstNonNull(T...)wurde aus dieser entnommen Antwort . In verwandten Fragen wurden auch verschiedene Ansätze vorgestellt .

lczapski
quelle