2015-02-14 8 views
33

Szukasz sposobu na łańcuchowe opcje, aby zwracany był pierwszy, który jest obecny. Jeśli żadna nie jest obecna, należy zwrócić kod Optional.empty().Opcje łańcuchowe w języku Java 8

Zakładając Mam kilka metod tak:

Optional<String> find1() 

Próbuję łańcucha im:

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

ale oczywiście to nie działa, ponieważ orElse spodziewa wartość i orElseGet oczekuje a Supplier.

+3

Wersja, która oczekuje, że 'Dostawca' będzie' .orElseGet() '. – glglgl

Odpowiedz

47

Użyj strumieniowe:

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

Jeśli trzeba ocenić sposoby znajdują się leniwie, używać funkcji dostawca:

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

findFirst() faktycznie zwraca pusty opcjonalny, jeśli nie znaleziono żadnych przedmiotów, więc usunę orElse(). –

+1

Twój jest nawet lepszy niż mój - oprócz czytelności: pierwsza "mapa" może być zmieniona na '.map (Dostawca :: get)' a druga na '.map (Opcjonalnie :: get)'. Kiedy już to zrobisz, '.filter (opcjonalnie :: isPresent)' również będzie w porządku, ale to nie zaszkodzi czytelności tak samo jak innym. – glglgl

+6

Działa to jednak w swoim rodzaju dużo kodu dla prostego problemu. Sprawia, że ​​myślę, że powinien on zostać wbudowany w Opcjonalne api – piler

22

Można to zrobić tak:

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

Chociaż nie jestem pewien, że to poprawia czytelność IMO. Guawa zapewnia sposób optionals łańcuchowych:

import com.google.common.base.Optional; 

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

To może być kolejna alternatywa dla swojego problemu, ale nie wykorzystuje standardową klasę opcjonalne w JDK.

Jeśli chcesz zachować standardowe API, można napisać prosty sposób użytkowy:

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

a następnie:

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

Jeśli masz dużo optionals do łańcuchów, może to lepiej używać metody Stream, jak już wspomniano.

+2

Rozwiązanie z wykorzystaniem metody narzędziowej jest prawdopodobnie tak czytelne, jak tylko może. Szkoda, że ​​Opcjonalne java java nie ma tej samej metody, co Guawa – piler

+3

'lub (Opcjonalne ... opts)' byłoby usprawnieniem. Varargs z lekami generycznymi to niefortunna mieszanka. –

+1

Twoje pierwsze podejście może rzucić wyjątek, nawet jeśli 'find1' lub' find2' zwróci wartość. – zeroflagL

-2

Może któryś z

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

zrobi.

Pierwsza iteracja nad szeregiem dostawców. Pierwszy niepusty numer Optional<> jest zwracany. Jeśli go nie znajdziemy, zwracamy pusty Optional.

Drugi ma to samo z Stream z Suppliers przez który przechodzi, każdy zadawane (leniwie) do wartości, która jest następnie filtrowany pustych Optional s. Pierwszy niepusty jest zwracany lub, jeśli taki nie istnieje, pusty.

+1

Wow, znowu -1 w/o zauważ, co może być ulepszony. – glglgl

+0

Ponieważ powyższe odpowiedzi robią dokładnie to samo w dużo bardziej przejrzysty i elegancki sposób. – Innokenty

8

Zainspirowany odpowiedzią Sauliego, możliwe jest użycie metody flatMap().

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

Konwersja Opcjonalnie do strumienia jest kłopotliwa. Najwyraźniej będzie to fixed with JDK9.Więc to może być zapisany jako

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

po aktualizacji Javy 9 ukazał

Chociaż oryginalne pytanie o Java 8, Optional::or został wprowadzony w Javie 9. Wraz z nim, problem może być rozwiązany jako podąża za

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