2015-02-14 11 views
33

Suchen nach einer Möglichkeit, Optionale zu verketten, so dass die erste zurückgegeben wird, die vorhanden ist. Wenn keine vorhanden sind, sollte Optional.empty() zurückgegeben werden.Verketten von Optionalen in Java 8

Angenommen, ich habe mehrere Methoden wie folgt aus:

Optional<String> find1() 

ich Kette bin versucht sie:

Optional<String> result = find1().orElse(this::find2).orElse(this::find3); 

aber das ist natürlich nicht, weil orElse funktioniert erwartet einen Wert und orElseGet erwartet a Supplier.

+3

Die Version, die ein 'Supplier' erwartet, wäre' .orElseGet() '. – glglgl

Antwort

47

Verwenden Sie einen Stream:

Stream.of(find1(), find2(), find3()) 
    .filter(Optional::isPresent) 
    .map(Optional::get) 
    .findFirst(); 

Wenn Sie die Such-Methoden lazily bewerten müssen, der Lieferanten Funktionen:

Stream.of(this::find1, this::find2, this::find3) 
    .map(Supplier::get) 
    .filter(Optional::isPresent) 
    .map(Optional::get) 
    .findFirst(); 
+1

findFirst() gibt tatsächlich ein leeres optionales Element zurück, wenn keine Elemente gefunden werden. Daher entferne ich orElse(). –

+1

Deines ist sogar besser als meins - abgesehen von der Lesbarkeit: amybe die erste 'map' könnte zu' .map (Supplier :: get) 'und die zweite zu' .map (Optional :: get) 'geändert werden. Wenn Sie dabei sind, wäre '.filter (Optional :: isPresent)' auch in Ordnung, aber das schadet der Lesbarkeit nicht so sehr wie die anderen. – glglgl

+6

Das funktioniert, obwohl es eine Menge Code für ein einfaches Problem ist. Lässt mich denken, dass es in der optionalen api – piler

22

Man könnte es wie folgt tun:

Optional<String> resultOpt = Optional.of(find1() 
           .orElseGet(() -> find2() 
           .orElseGet(() -> find3() 
           .orElseThrow(() -> new WhatEverException())))); 

Obwohl ich nicht sicher bin, es Lesbarkeit IMO verbessert. Guava bietet eine Möglichkeit zur Kette Optionals:

import com.google.common.base.Optional; 

Optional<String> resultOpt = s.find1().or(s.find2()).or(s.find3()); 

Es eine weitere Alternative für Ihr Problem sein könnte, aber verwendet nicht den Standard Optional Klasse im JDK.

Wenn Sie den Standard-API halten möchten, können Sie eine einfache Dienstprogramm Methode schreiben:

static <T> Optional<T> or(Optional<T> first, Optional<T> second) { 
    return first.isPresent() ? first : second; 
} 

und dann:

Optional<String> resultOpt = or(s.find1(), or(s.find2(), s.find3())); 

Wenn Sie eine Menge Extras zu Ketten haben, vielleicht ist es besser, den Stream-Ansatz zu verwenden, als andere bereits erwähnt.

+2

Die Lösung mit der Dienstprogrammmethode ist wahrscheinlich so leserlich wie möglich. Schade, Java's Optional hat nicht die gleiche Methode wie die Guava one – piler

+3

'oder (Optional ... opts)' wäre eine Verbesserung. Varargs mit Generika sind jedoch eine unglückliche Mischung. –

+1

Ihr erster Ansatz könnte eine Ausnahme auslösen, selbst wenn 'find1' oder' find2' einen Wert zurückgeben würde. – zeroflagL

-2

Vielleicht eines der

public <T> Optional<? extends T> firstOf(Optional<? extends T> first, @SuppressWarnings("unchecked") Supplier<Optional<? extends T>>... supp) { 
     if (first.isPresent()) return first; 
     for (Supplier<Optional <? extends T>> sup : supp) { 
      Optional<? extends T> opt = sup.get(); 
      if (opt.isPresent()) { 
       return opt; 
      } 
     } 
     return Optional.empty(); 
    } 

    public <T> Optional<? extends T> firstOf(Optional<? extends T> first, Stream<Supplier<Optional<? extends T>>> supp) { 
     if (first.isPresent()) return first; 
     Stream<Optional<? extends T>> present = supp.map(Supplier::get).filter(Optional::isPresent); 
     return present.findFirst().orElseGet(Optional::empty); 
    } 

tun.

Die erste iteriert über eine Reihe von Lieferanten. Die erste nicht leere Optional<> wird zurückgegeben. Wenn wir keine finden, geben wir eine leere Optional zurück.

Die zweite tut dasselbe mit einem Stream von Suppliers die durchlaufen wird, die jeweils aufgefordert (lazily) für ihren Wert, der dann für leere Optional s gefiltert. Der erste nicht leere wird zurückgegeben, oder, wenn kein solcher existiert, ein leerer.

+1

Wow, wieder ein -1 w/o bemerken, was sein könnte verbessert. – glglgl

+0

Da die Antworten oben genau dasselbe in viel klarer und eleganter Weise tun. – Innokenty

8

Inspiriert von Sauli's Antwort ist es möglich, die flatMap() Methode zu verwenden.

Stream.of(this::find1, this::find2, this::find3) 
    .map(Supplier::get) 
    .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty)) 
    .findFirst(); 

Die Konvertierung eines optionalen in einen Stream ist umständlich. Anscheinend wird dies fixed with JDK9 sein.So könnte dies als

Stream.of(this::find1, this::find2, this::find3) 
    .map(Supplier::get) 
    .flatMap(Optional::stream) 
    .findFirst(); 

-Update geschrieben werden, nachdem Java 9 veröffentlicht wurde

Obwohl die ursprüngliche Frage war etwa Java 8, Optional::or in Java 9. Mit ihm eingeführt wurde, konnte das Problem gelöst werden folgt

Optional<String> result = find1() 
    .or(this::find2) 
    .or(this::find3);