2017-02-23 75 views
3

我有一些情況下,使用Java 8 Stream使我重複執行一些操作,如果沒有流,可以避免它,但我認爲問題不是與流,但我。如何做到過濾器和映射沒有重複操作的開銷

一些示例:

private class Item { 
    String id; 
    List<String> strings; 
} 

// This method, filters only the Items that have the strToFind, and 
// then maps it to a new string, that has the id and the str found 
private void doIt(List<Item> items, String strToFind) { 
    items.stream().filter(item -> { 
     return item.strings.stream().anyMatch(str -> this.operation(str, strToFind)); 
    }).map(item -> { 
     return item.id + "-" + item.strings.stream() 
      .filter(str -> this.operation(str, strToFind)).findAny().get(); 
    }); 
} 

// This operation can have a lot of overhead, therefore 
// it would be really bad to apply it twice 
private boolean operation(String str, String strToFind) { 
    return str.equals(strToFind); 
} 

正如你所看到的,功能operation被調用兩次爲每個項目,我不希望出現這種情況。我首先想到的是直接映射並返回「null」,如果沒有找到,然後過濾空值,但如果我這樣做,我將失去對Item的引用,因此不能使用id。

+0

我猜是有一個更聰明的選擇,但在'map'-then-'filter'之後,就像你建議的那樣,使用'reduce'來選擇性地轉換並推送到一個新列表。 – user650881

+0

在這種情況下,'item.strings.stream()。filter(str - > this.operation(str,strToFind))。findAny()。get()'可以被'strToFind'替換,但我猜'操作'不是那樣實際執行的? –

+0

@JornVernee對,我把一個'equals'代表一個操作,但這可能是不同的東西。我沒有把原來的代碼,因爲是很多代碼.. –

回答

3

您可以使用

private void doIt(List<Item> items, String strToFind) { 
    items.stream() 
     .flatMap(item -> item.strings.stream().unordered() 
      .filter(str -> this.operation(str, strToFind)).limit(1) 
      .map(string -> item.id + "-" + string)) 
     // example terminal operation 
     .forEach(System.out::println); 
} 

.unordered().limit(1)存在產生類似anyMatch()相同的行爲和原始代碼的findAny()。當然,.unordered()不需要得到正確的結果。

在Java 9,你也可以使用

private void doIt(List<Item> items, String strToFind) { 
    items.stream() 
     .flatMap(item -> item.strings.stream() 
      .filter(str -> this.operation(str, strToFind)) 
      .map(string -> item.id + "-" + string).findAny().stream()) 
     // example terminal operation 
     .forEach(System.out::println); 
} 

保持findAny()操作,但不幸的是,Java的8缺少Optional.stream()方法,並試圖模仿它會創建代碼比limit(1)方法的可讀性。

+0

我認爲這個解決方案更好,因爲你不必認爲像返回null和類似的東西。十分優雅 ! –

5

我想你可能希望這種行爲:

items.stream().map(item -> { 
     Optional<String> optional = item.strings.stream().filter(string -> operation(string, strToFind)).findAny(); 
     if(optional.isPresent()){ 
      return item.id + "-" + optional.get(); 
     } 
     return null; 
    }).filter(e -> e != null); 

編輯: 因爲你失去的時候你在做以後在地圖過濾器獲得的信息,但沒有什麼能阻止你做的工作僅在地圖中,然後過濾。

編輯2: 作爲@Jorn Vernee指出的那樣,你可以進一步縮短:

private void doIt(List<Item> items, String strToFind) { 
    items.stream().map(item -> item.strings.stream().filter(string -> operation(string, strToFind)).findAny() 
      .map(found -> item.id + "-" + found).orElse(null)).filter(e -> e != null); 
} 
+1

你可以用'return optional.map(str - > item.id +「 - 」+ str)。orElse(null)'替換'if'和'return'。或者讓整件事情成爲一線。 –

+0

@Jorn Vernee非常真實,我沒有看到它那麼遠。 –

2

雖然不是最短的代碼(但這並沒有被要求)我相信這使用Optional非常簡單,但不涉及任何null映射和/或檢查和類型信息(字符串對象)不會意外丟失:

items.stream() 
    .map(item -> item.strings.stream() 
     .filter(str -> this.operation(str, strToFind)) 
     .findAny() 
     .<String>map(string -> item.id + "-" + string)) 
    .filter(Optional::isPresent) 
    .map(Optional::get); 

這幾乎是Jeremy Grand's和Holger答案的結合。