2014-09-30 19 views
7

Już widziałem to wiele razy sam, tego jestem pewien:Czy istnieje elegancki sposób na uzyskanie pierwszej wartości zerowej wielokrotnych zwracanych metod w języku Java?

public SomeObject findSomeObject(Arguments args) { 
    SomeObject so = queryFirstSource(args); // the most likely source first, hopefully 
    if (so != null) return so; 

    so = querySecondSource(args); // a source less likely than the first, hopefully 
    if (so != null) return so; 

    so = queryThirdSource(args); // a source less likely than the previous, hopefully 
    if (so != null) return so; 

    // and so on 
} 

Mamy różne źródła, gdy obiekt szukamy może być. Jako bardziej żywy przykład możemy sobie wyobrazić, że najpierw sprawdzamy, czy identyfikator użytkownika znajduje się na liście uprzywilejowanych użytkowników. Jeśli nie, sprawdzamy, czy identyfikator użytkownika znajduje się na liście dozwolonych użytkowników. W przeciwnym razie zwracamy zero. (To nie jest najlepszy przykład, ale mam nadzieję, że jest to jeden żywy-wystarczy.)

Guava oferuje nam kilka pomocników, które mogą upiększyć ten kod powyżej:

public SomeObject findSomeObject(Arguments args) { 
    // if there are only two objects 
    return com.google.common.base.Objects.firstNonNull(queryFirstSource(args), querySecondSource(args)); 

    // or else 
    return com.google.common.collect.Iterables.find(
     Arrays.asList(
      queryFirstSource(args) 
      , querySecondSource(args) 
      , queryThirdSource(args) 
      // , ... 
     ) 
     , com.google.common.base.Predicates.notNull() 
    ); 
} 

Ale, jako bardziej doświadczony spośród będzie nam mieć już widać, może to być złe, jeśli wyszukiwania (np. queryXXXXSource(args)) zajmują pewien czas. Dzieje się tak dlatego, że najpierw wysyłamy zapytania do wszystkich źródeł, a następnie przekazujemy wyniki do metody, która znajduje pierwszy z tych wyników, który nie jest null.

W przeciwieństwie do pierwszego przykładu, gdzie następne źródło jest oceniane tylko wtedy, gdy pierwszy nie zwraca czegoś, to drugie rozwiązanie może początkowo wyglądać lepiej, ale może działać znacznie gorzej.

Oto, gdzie dochodzimy do mojego aktualnego pytania i gdzie proponuję coś z tego, mam nadzieję, że ktoś już wdrożył jego bazę lub że ktoś może zaproponować nawet sprytne rozwiązanie.

W prostym języku angielskim: Czy ktoś już zaimplementował takie defferedFirstNonNull (patrz poniżej) lub coś podobnego? Czy istnieje proste rozwiązanie Java w celu uzyskania tego dzięki nowej strukturze Stream? Czy możesz zaproponować inne eleganckie rozwiązanie, które zapewnia taki sam efekt?

Zasady: Java 8 jest dozwolone, a także aktywny utrzymany i dobrze znanych bibliotek stron trzecich, takich jak Google Guava lub Apache Commons Lang z licencją Apache lub podobnego (Nie GPL!).

Proponowane rozwiązanie:

public SomeObject findSomeObject(Arguments args) { 
    return Helper.deferredFirstNonNull(
     Arrays.asList(
      args -> queryFirstSource(args) 
      , args -> querySourceSource(args) 
      , args -> queryThirdSource(args) 
     ) 
     , x -> x != null 
    ) 
} 

Więc metoda defferedFirstNonNull oceni każde wyrażenie lambda po drugim i tak szybko, jak orzecznika (x -> x != null) jest prawdziwe (tzn okazało mecz) metoda zwróci wynik natychmiast i nie będzie pytać o żadne inne źródło.

PS: Wiem, że wyrażenia args -> queryXXXXSource(args) mogą zostać skrócone do queryXXXXSource. Ale to spowodowałoby, że proponowane rozwiązanie byłoby trudniejsze do odczytania, ponieważ na pierwszy rzut oka nie jest oczywiste, co się stanie.

Odpowiedz

7

To zależy od niektórych czynników, których nie definiujesz. Czy masz ustalony, raczej niewielki zestaw działań query…Source, jak pokazano w twoim pytaniu, czy raczej zmierzasz do posiadania bardziej elastycznej, rozszerzalnej listy działań?

W pierwszym przypadku można rozważyć zmianę metody query…Source do zwracania Optional<SomeObject> zamiast SomeObject lub null. Jeśli zmienisz swoje metody być jak

Optional<SomeObject> queryFirstSource(Arguments args) { 
    … 
} 

Można łańcucha im w ten sposób:

public SomeObject findSomeObject(Arguments args) { 
    return queryFirstSource(args).orElseGet(
    ()->querySecondSource(args).orElseGet(
    ()->queryThirdSource(args).orElse(null))); 
} 

Jeśli nie można ich zmienić lub wolą ich do powrotu null nadal można używać klasy Optional:

public SomeObject findSomeObject(Arguments args) { 
    return Optional.ofNullable(queryFirstSource(args)).orElseGet(
     ()->Optional.ofNullable(querySecondSource(args)).orElseGet(
     ()->queryThirdSource(args))); 
} 

Jeśli szukasz bardziej elastycznym sposobem na większa liczba możliwych zapytań, nieuniknione jest przekonwertowanie ich na jakąś listę lub strumień Function s. Jednym z możliwych rozwiązań jest:

public SomeObject findSomeObject(Arguments args) { 
    return Stream.<Function<Arguments,SomeObject>>of(
     this::queryFirstSource, this::querySecondSource, this::queryThirdSource 
    ).map(f->f.apply(args)).filter(Objects::nonNull).findFirst().orElse(null); 
} 

ten wykonuje żądaną operację, jednak będzie on komponować niezbędne działania za każdym razem wywołać metodę. Jeśli chcesz częściej wywołać tę metodę, można rozważyć komponowania operacji, które można ponownie wykorzystać:

Function<Arguments, SomeObject> find = Stream.<Function<Arguments,SomeObject>>of(
    this::queryFirstSource, this::querySecondSource, this::queryThirdSource 
).reduce(a->null,(f,g)->a->Optional.ofNullable(f.apply(a)).orElseGet(()->g.apply(a))); 

public SomeObject findSomeObject(Arguments args) { 
    return find.apply(args); 
} 

Więc widać, istnieje więcej niż jeden sposób. I to zależy od faktycznego zadania, w jakim kierunku iść. Czasami odpowiednia może być nawet prosta sekwencja if.

+0

Wybrałem tę odpowiedź jako moją wyznaczoną odpowiedź, ponieważ łączy ona wszystkie inne odpowiedzi i wydaje się zatem" kompletna ". Nie wiedziałem o klasie "Optional <>" i uważam, że zapewnia ona najbardziej elegancki sposób na mój problem. – cimnine

10

Tak, jest:

Arrays.asList(source1, source2, ...) 
    .stream() 
    .filter(s -> s != null) 
    .findFirst(); 

To jest bardziej elastyczny, ponieważ zwraca Optional nie null w przypadku nie-zerowy source znaleziono.

Edit: Jeśli chcesz wartościowanie leniwe należy użyć Supplier:

Arrays.<Supplier<Source>>asList(sourceFactory::getSource1, sourceFactory::getSource2, ...) 
    .stream() 
    .filter(s -> s.get() != null) 
    .findFirst(); 
+0

Jeśli chcesz utworzyć 'Stream' z varargs, możesz równie dobrze wywołać' Stream.of (source1, source2, ...) 'zamiast' Arrays.asList (source1, source2, ...) .stream() ' –

5

chciałbym napisać to tak (może nie trzeba rodzajowych tutaj, ale dlaczego nie zrobić):

public static <A, T> Optional<T> findFirst(Predicate<T> predicate, A argument, 
             Function<A, T>... functions) { 
    return Arrays.stream(functions) 
      .map(f -> f.apply(argument)) 
      .filter(predicate::test) 
      .findFirst(); 
} 

I można nazwać go:

return findFirst(Objects::nonNull, args, this::queryFirstSource, 
             this::querySecondSource, 
             this::queryThirdSource); 

(zakładając, że spełnione queryXXX hods są metodami instancji)

Metody zostaną zastosowane w kolejności, dopóki nie zwróci się wartość zgodna z predykatem (w powyższym przykładzie: zwraca wartość inną niż null).