【问题标题】:How to do filter and map without overhead of repeating operation如何在没有重复操作开销的情况下进行过滤和映射
【发布时间】:2017-07-14 08:18:10
【问题描述】:

在某些情况下,使用 Java 8 Stream 会使我重复执行某些操作,如果不使用 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。

【问题讨论】:

  • 我猜有一个更聪明的选择,但就像你建议的那样,在map-then-filter 之后出现了使用reduce 有选择地转换并推送到新列表的想法。
  • 在这种情况下,item.strings.stream().filter(str -&gt; this.operation(str, strToFind)).findAny().get() 可以被strToFind 替换,但我猜operation 实际上并没有这样实现?
  • @JornVernee 是的,我放了一个 equals 来代表一个操作,但这可能是不同的东西。我没有放原始代码,因为代码很多..
  • 我发现不用水平滚动阅读这篇文章要容易得多,但如果您反对,请随时恢复我的编辑。
  • @DavidConrad 没问题!我也觉得更容易:)

标签: java performance java-stream


【解决方案1】:

我想你可能想要这种行为:

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

【讨论】:

  • 您可以将ifreturn 替换为return optional.map(str -&gt; item.id + "-" + str).orElse(null)。或者把整个事情变成一条线。
  • @Jorn Vernee 非常正确,我还没查到那么远。
【解决方案2】:

你可以使用

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) 方法可读性差的代码。

【讨论】:

  • 我认为这个解决方案更好,因为您不必考虑返回 null 之类的东西。非常优雅!
【解决方案3】:

虽然不是最短的代码(但没有被要求),但我相信使用 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 的答案的结合。

【讨论】:

    猜你喜欢
    • 2010-10-28
    • 2018-01-08
    • 2011-06-14
    • 2014-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    相关资源
    最近更新 更多