webentwicklung-frage-antwort-db.com.de

Gibt den ersten Wert ungleich Null zurück

Ich habe eine Reihe von Funktionen:

String first(){}
String second(){}
...
String default(){}

Jeder kann einen Nullwert zurückgeben, mit Ausnahme des Standardwerts. Jede Funktion kann unterschiedliche Parameter annehmen. Zum Beispiel könnte erstens kein Argument, zweitens ein String, drittens drei Argumente usw. Enthalten.

ObjectUtils.firstNonNull(first(), second(), ..., default());

Das Problem ist, dass dies aufgrund des Funktionsaufrufs eine eifrige Auswertung ist. Wo möchte ich vorzeitig beenden, nach der zweiten Funktion sagen (weil die Funktionsaufrufe teuer sein können, API-Aufrufe denken, usw.). In anderen Sprachen können Sie etwas Ähnliches tun:

return first() || second() || ... || default()

In Java weiß ich, dass ich Folgendes tun kann:

String value;
if (value = first()) == null || (value = second()) == null ...
return value;

Das ist nicht sehr lesbar, IMO, da == null Prüfungen . ObjectUtils.firstNonNull () zuerst eine Sammlung erstellt und dann iteriert, was in Ordnung ist, solange die Funktion träge ausgewertet wird.

Vorschläge? (neben ein paar ifs)

21
lorenzocastillo
String s = Stream.<Supplier<String>>of(this::first, this::second /*, ... */)
                 .map(Supplier::get)
                 .filter(Objects::nonNull)
                 .findFirst()
                 .orElseGet(this::defaultOne);

Es stoppt beim ersten Nicht-Null-Wert oder setzt den Wert, der von defaultOne zurückgegeben wird. Solange Sie fortlaufend bleiben, sind Sie in Sicherheit. Dies setzt natürlich Java 8 oder höher voraus.

Der Grund, warum es beim ersten Auftreten eines Nicht-Null-Werts angehalten wird, liegt darin, wie Stream mit jedem Schritt umgeht. Das map ist ein Zwischenoperation , ebenso das filter . Das findFirst auf der anderen Seite ist ein Kurzschlussklemmenbetrieb . Also geht es mit dem nächsten Element weiter, bis man mit dem Filter übereinstimmt. Wenn kein Element mit einem leeren übereinstimmt, wird ein optionales zurückgegeben, und der orElseGet - Lieferant wird aufgerufen.

this::first usw. sind nur Methodenreferenzen. Wenn sie statisch sind, ersetzen Sie sie durch YourClassName::first, etc.

Hier ist ein Beispiel, wenn die Signatur Ihrer Methoden abweichen würde:

String s = Stream.<Supplier<String>>of(() -> first("takesOneArgument"),
                                       () -> second("takes", 3, "arguments")
                                   /*, ... */)
                 .map(Supplier::get)
                 .filter(Objects::nonNull)
                 .findFirst()
                 .orElseGet(this::defaultOne);

Beachten Sie, dass Supplier nur ausgewertet wird, wenn Sie get aufrufen. Auf diese Weise erhalten Sie Ihr faules Bewertungsverhalten. Die Methodenparameter in Ihrem Lieferanten-Lambda-Ausdruck müssen endgültig oder effektiv endgültig sein.

34
Roland

Dies kann mit einem Stream von Suppliers ziemlich sauber gemacht werden.

Optional<String> result = Stream.<Supplier<String>> of(
     () -> first(), 
     () -> second(),
     () -> third() )
  .map( x -> x.get() )
  .filter( s -> s != null)
  .findFirst(); 

Der Grund, warum dies funktioniert, ist, dass trotz des Auftretens die gesamte Ausführung von findFirst() gesteuert wird, das ein Element aus filter() abzieht, das träge Elemente aus map() abzieht ruft get() auf, um jeden Pull zu verarbeiten. findFirst() beendet das Abrufen aus dem Stream, wenn ein Element den Filter passiert hat, sodass nachfolgende Lieferanten nicht get() aufgerufen haben.

Obwohl ich persönlich den deklarativen Stream-Stil klarer und aussagekräftiger finde, muss Sie nicht Stream verwenden, um mit Suppliers zu arbeiten, wenn Sie dies nicht möchten der Style:

Optional<String> firstNonNull(List<Supplier<String>> suppliers {
    for(Supplier<String> supplier : suppliers) {
        String s = supplier.get();
        if(s != null) {
            return Optional.of(s);
        }
    }
    return Optional.empty();
}

Es sollte offensichtlich sein, dass Sie anstelle von Optional auch String zurückgeben können, indem Sie entweder null (yuk), eine Standardzeichenfolge zurückgeben oder eine Ausnahme auslösen, wenn Sie Optionen aus der Liste ausschließen.

9
slim

Es ist nicht lesbar, da es sich um eine Reihe separater Funktionen handelt, die keinerlei Verbindung miteinander zum Ausdruck bringen. Wenn Sie versuchen, sie zusammenzusetzen, ist der Mangel an Richtung offensichtlich.

Versuchen Sie es stattdessen

 public String getFirstValue() {
      String value;
      value = first();
      if (value != null) return value;
      value = second();
      if (value != null) return value;
      value = third();
      if (value != null) return value;
      ...
      return value;
 }

Wird es lange dauern Wahrscheinlich. Sie wenden jedoch Code auf eine Oberfläche an, die für Ihre Vorgehensweise nicht geeignet ist.

Wenn Sie nun die Benutzeroberfläche ändern könnten, würden Sie die Benutzeroberfläche möglicherweise benutzerfreundlicher gestalten. Ein mögliches Beispiel wäre, dass die Schritte "ValueProvider" -Objekte sind.

public interface ValueProvider {
    public String getValue();
}

Und dann könnte man es gerne benutzen

public String getFirstValue(List<ValueProvider> providers) {
    String value;
    for (ValueProvider provider : providers) {
       value = provider.getValue();
       if (value != null) return value;
    }
    return null;
}

Es gibt verschiedene andere Ansätze, für die jedoch eine objektorientiertere Umstrukturierung des Codes erforderlich ist. Denken Sie daran, nur weil Java eine objektorientierte Programmiersprache ist, heißt das nicht, dass sie immer objektorientiert verwendet wird. Die first()... last() Methodenauflistung ist sehr nicht objektorientiert, da sie kein List modelliert. Obwohl die Methodennamen aussagekräftig sind, enthält ein List Methoden, die darauf basieren Ermöglichen eine einfache Integration mit Werkzeugen wie for Schleifen und Iterators.

3
Edwin Buck

Wenn Sie Java 8 verwenden, können Sie diese Funktionsaufrufe in Lambdas konvertieren.

public static<T> T firstNonNull(Supplier<T> defaultSupplier, Supplier<T>... funcs){
    return Arrays.stream(funcs).filter(p -> p.get() != null).findFirst().orElse(defaultSupplier).get();
}

Wenn Sie die generische Implementierung nicht möchten und sie nur für String verwenden, fahren Sie fort und ersetzen Sie T durch String:

public static String firstNonNull(Supplier<String> defaultSupplier, Supplier<String>... funcs){
    return Arrays.stream(funcs).filter(p -> p.get() != null).findFirst().orElse(defaultSupplier).get();
}

Und dann nenne es so:

firstNonNull(() -> getDefault(), () -> first(arg1, arg2), () -> second(arg3));

P.S. btw default ist ein reserviertes Schlüsselwort, daher können Sie es nicht als Methodennamen verwenden :)

BEARBEITEN: OK, der beste Weg, dies zu tun, wäre die Rückgabe von Optional, dann müssen Sie den Standardlieferanten nicht separat übergeben:

@SafeVarargs
public static<T> Optional<T> firstNonNull(Supplier<T>... funcs){
    return Arrays.stream(funcs).filter(p -> p.get() != null).map(s -> s.get()).findFirst();
}
2
Nestor Sokil

Wenn Sie es in eine Utility-Methode packen möchten, müssen Sie jede Funktion in etwas packen, das die Ausführung verzögert. Vielleicht so etwas:

public interface Wrapper<T> {
    T call();
}

public static <T> T firstNonNull(Wrapper<T> defaultFunction, Wrapper<T>... funcs) {
    T val;
    for (Wrapper<T> func : funcs) {
       if ((val = func.call()) != null) {
           return val;
       }
    }
    return defaultFunction.call();
}

Sie könnten Java.util.concurrent.Callable Verwenden, anstatt Ihre eigene Wrapper -Klasse zu definieren, aber dann müssten Sie mit der Ausnahme umgehen, dass Callable.call() als ausgelöst deklariert wird.

Dies kann dann aufgerufen werden mit:

String value = firstNonNull(
    new Wrapper<>() { @Override public String call() { return defaultFunc(); },
    new Wrapper<>() { @Override public String call() { return first(); },
    new Wrapper<>() { @Override public String call() { return second(); },
    ...
);

In Java 8 können Sie, wie @dorukayhan betont, auf die Definition Ihrer eigenen Wrapper -Klasse verzichten und einfach die Supplier -Schnittstelle verwenden. Auch der Aufruf kann mit Lambdas viel sauberer gemacht werden:

String value = firstNonNull(
    () -> defaultFunc(),
    () -> first(),
    () -> second(),
    ...
);

Sie können auch (wie @Oliver Charlesworth vorschlägt) Methodenreferenzen als Kurzform für die Lambda-Ausdrücke verwenden:

String value = firstNonNull(
    MyClass::defaultFunc,
    MyClass::first,
    MyClass::second,
    ...
);

Ich habe zwei Meinungen darüber, was lesbarer ist.

Alternativ können Sie eine der Streaming-Lösungen verwenden, die in vielen anderen Antworten vorgeschlagen wurden.

1
Ted Hopp

Machen Sie einfach eine Klasse mit einer Funktion wie folgt:

class ValueCollector {
  String value;
  boolean v(String val) { this.value = val; return val == null; }
}

ValueCollector c = new ValueCollector();
if c.v(first()) || c.v(second()) ...
return c.value;
0
DenisSt