2017-02-23 11 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"を返し、その後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で、あなたもfindAny()操作を維持

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

使用することができますが、残念ながら、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); 

EDIT:あなたは後でマップをやっているフィルタで得られた情報を失っているので が、何も操作をしてからあなたを防ぎます地図上にのみ表示され、後でフィルタリングされます。

EDIT 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

'if'と' return'を 'return optional.map(str-> item.id +" - "+ str).orElse(null)'で置き換えることができます。または、すべてのものを1つのライナーにする。 –

+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とHolgerの回答はほぼ同じです。

関連する問題