2017-02-23 46 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答案的结合。