2014-09-30 62 views
7

你已經看到過很多次自己,那我敢肯定:有沒有一種優雅的方式來獲得Java中的多個方法返回的第一個非null值?

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 
} 

我們有不同的來源,我們搜索的對象可以是。作爲一個更生動的例子,我們可以想象,我們首先檢查用戶標識是否在特權用戶列表中。如果不是,我們檢查用戶標識是否在允許的用戶列表中。否則我們返回null。 (這是不是最好的例子,但我希望這是一個生動的,足以之一。)

Guava我們提供了一些助手,可以美化上面代碼:

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

但是,隨着更多的經驗中我們將有已經看到,如果查找(即queryXXXXSource(args))需要一定的時間,這可能會表現不佳。這是因爲我們現在先查詢所有來源,然後將結果傳遞給在null之外的結果中找到第一個的方法。

與第一個例子相反,在第一個例子中,只有當前者不返回某些東西時才評估下一個數據源,這個第二個解決方案起初看起來可能更好,但可能會更糟糕。

這裏是我們談到我的實際問題的地方,以及我建議的一些事情,我希望有人已經實施了它的基礎,或者有人可能會提出一個甚至是聰明的解決方案。

用簡單的英語:已經有人已經實施了類似這樣的defferedFirstNonNull(見下文)的東西?有沒有一個簡單的Java解決方案來實現這個新的Stream框架?你能否提出另一個可以達到相同結果的優雅解決方案?

規則:的Java 8被允許以及積極維護和知名第三方庫像谷歌的番石榴或Apache許可證或類似Apache的共享郎(沒有GPL!)。

提出的解決方案:

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

因此該方法defferedFirstNonNull將陸續評估每個lambda表達式,並儘快作謂語(x -> x != null)是真實的(即我們找到了一個匹配)的方法將返回結果立即並不會查詢任何進一步的來源。

PS:我知道表達式args -> queryXXXXSource(args)可以縮短爲queryXXXXSource。但是這會使提出的解決方案難以閱讀,因爲一見鍾情並不明顯。

回答

7

這取決於你沒有定義的一些因素。你是否有固定的,很小的一組query…Source行動,如你的問題所示,或者你是否想要有一個更靈活,可擴展的行動列表?

在第一種情況下,你可能會考慮改變query…Source方法返回一個Optional<SomeObject>而非SomeObjectnull。如果你改變你的方法是像

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

你可以把它們連這樣:

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

如果你不能改變他們還是更喜歡他們返回null你仍然可以使用Optional類:

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

如果你正在尋找一個更靈活的方式更大數量的可能查詢,它是不可避免的將它們轉換爲Function s的某種列表或流。一個可能的解決方案是:

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

該執行所需的操作,但是,它會在您每次調用方法時,構成必要的行動。如果您希望更頻繁地調用這個方法,你可以考慮組成,你可以重新使用一個操作:

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

所以你看,有不止一種方法。這取決於實際任務的走向。有時,即使是簡單的if序列也可能是合適的。

+0

我選擇了這個答案作爲我指定的答案,因爲它結合了所有其他答案,因此似乎'完整'。我不知道'Optional <>'類,我認爲它爲我的問題提供了最優雅的方式。 – cimnine 2014-10-02 14:43:52

10

是的,有:

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

這是更靈活,因爲它返回一個Optionalnull的情況下,非空source被發現。

編輯:如果你想偷懶的評價,你應該使用Supplier

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

如果你想從可變參數中創建一個'Stream',你可以直接調用'Stream.of(source1,source2,...)'而不是'Arrays.asList(source1,source2,...) .stream()' – 2017-03-23 00:40:59

5

我會寫這樣的(你可能不在這裏需要仿製藥,但爲什麼不這樣做):

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

而且你可以把它叫做:

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

(假設你的queryXXX滿足hods是實例方法)

該方法將按順序應用,直到返回一個與謂詞相匹配的值(在上例中:返回非空值)。