【问题标题】:Find first element by predicate按谓词查找第一个元素
【发布时间】:2014-07-04 23:13:45
【问题描述】:

我刚刚开始使用 Java 8 lambda,我正在尝试实现一些我习惯于使用函数式语言的东西。

例如,大多数函数式语言都有某种查找函数,它对序列或返回第一个元素的列表进行操作,谓词为true。我认为在 Java 8 中实现这一目标的唯一方法是:

lst.stream()
    .filter(x -> x > 5)
    .findFirst()

但这对我来说似乎效率低下,因为过滤器将扫描整个列表,至少在我看来(这可能是错误的)。有没有更好的办法?

【问题讨论】:

  • 效率不低,Java 8 Stream 实现是惰性求值的,所以过滤器只适用于终端操作。同样的问题:stackoverflow.com/questions/21219667/stream-and-lazy-evaluation
  • 酷。这就是我希望它会做的事情。否则,这将是一次重大的设计失败。
  • 如果您的意图真的是检查列表是否包含这样的元素(而不是挑出可能的几个元素中的第一个), .findAny() 理论上可以在并行设置中更有效,并且当然可以更清楚地传达这种意图。
  • 与简单的 forEach 循环相比,这将在堆上创建大量对象和数十个动态方法调用。虽然这可能并不总是影响您的性能测试的底线,但在热点中,避免使用 Stream 和类似的重量级结构会有所不同。

标签: java java-8 java-stream


【解决方案1】:

不,过滤器不会扫描整个流。它是一个中间操作,它返回一个惰性流(实际上所有中间操作都返回一个惰性流)。为了说服您,您可以简单地进行以下测试:

List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
            .peek(num -> System.out.println("will filter " + num))
            .filter(x -> x > 5)
            .findFirst()
            .get();
System.out.println(a);

哪些输出:

will filter 1
will filter 10
10

您会看到实际上只处理了流的前两个元素。

所以你可以采用你的方法,这非常好。

【讨论】:

  • 作为说明,我在这里使用了get();,因为我知道我将哪些值提供给流管道,因此会有结果。在实践中,您不应使用get();,而应使用orElse() / orElseGet() / orElseThrow()(用于更有意义的错误而不是NSEE),因为您可能不知道应用于流管道的操作是否会导致一个元素。
  • .findFirst().orElse(null); 例如
  • 不要使用 orElse null。这应该是一种反模式。它都包含在 Optional 中,那么您为什么要冒 NPE 的风险呢?我认为处理 Optional 是更好的方法。只需在使用前使用 isPresent() 测试 Optional。
  • @BeJay 我不明白。我应该用什么来代替orElse?
  • @JohnHenckel 我认为 BeJay 的意思是您应该将其保留为 Optional 类型,这是 .findFirst 返回的内容。 Optional 的用途之一是帮助开发人员避免处理nulls。例如您可以检查myOptional.isPresent(),而不是检查myObject != null,或者使用Optional接口的其他部分。是不是更清楚了?
【解决方案2】:

但这对我来说似乎效率低下,因为过滤器会扫描整个列表

不,它不会 - 一旦找到满足谓词的第一个元素,它就会“中断”。您可以在stream package javadoc 中阅读更多关于懒惰的信息,尤其是(强调我的):

许多流操作(例如过滤、映射或重复删除)可以延迟实现,从而提供优化机会。例如,“查找具有三个连续元音的第一个字符串”不需要检查所有输入字符串。流操作分为中间(流产生)操作和终端(产生价值或副作用)操作。 中间操作总是懒惰的。

【讨论】:

  • 这个答案对我来说提供了更多信息,并解释了原因,而不仅仅是如何。我从不新的中间操作总是懒惰的; Java 流继续让我感到惊讶。
【解决方案3】:
return dataSource.getParkingLots()
                 .stream()
                 .filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
                 .findFirst()
                 .orElse(null);

我必须从对象列表中仅过滤掉一个对象。所以我用了这个,希望对你有帮助。

【讨论】:

  • 更好:因为我们寻找布尔返回值,我们可以通过添加空检查来做得更好: return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot .getId(), id)).findFirst().orElse(null) != null;
  • @shreedharbhat 你不需要做.orElse(null) != null。相反,请使用可选 API 的 .isPresent,即 .findFirst().isPresent()。
  • @shreedharbhat 首先,OP 不是在寻找布尔返回值。其次,如果是的话,写.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id)) 会更干净
  • 这个不错,效果很好。您可以在列表中获取整个对象,但是我想进行更正。你写了equals,但它是equal,如下:.filter(parkingLot -&gt; Objects.equal(parkingLot.getId(), id))
【解决方案4】:

除了Alexis C 的回答,如果您正在使用数组列表,而您不确定要在其中搜索的元素是否存在,请使用它。

Integer a = list.stream()
                .peek(num -> System.out.println("will filter " + num))
                .filter(x -> x > 5)
                .findFirst()
                .orElse(null);

然后你可以简单地检查 a 是否为null。

【讨论】:

【解决方案5】:

@AjaxLeung 已回答,但在 cmets 中很难找到。
仅供检查

lst.stream()
    .filter(x -> x > 5)
    .findFirst()
    .isPresent()

简化为

lst.stream()
    .anyMatch(x -> x > 5)

【讨论】:

    【解决方案6】:
    
    import org.junit.Test;
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.Optional;
    
    // Stream is ~30 times slower for same operation...
    public class StreamPerfTest {
    
        int iterations = 100;
        List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
    
    
        // 55 ms
        @Test
        public void stream() {
    
            for (int i = 0; i < iterations; i++) {
                Optional<Integer> result = list.stream()
                        .filter(x -> x > 5)
                        .findFirst();
    
                System.out.println(result.orElse(null));
            }
        }
    
        // 2 ms
        @Test
        public void loop() {
    
            for (int i = 0; i < iterations; i++) {
                Integer result = null;
                for (Integer walk : list) {
                    if (walk > 5) {
                        result = walk;
                        break;
                    }
                }
                System.out.println(result);
            }
        }
    }
    
    

    【讨论】:

    • 这就是我避免将流用于简单任务的原因。它通常比使用简单迭代要慢得多。 (如果你使用数组操作,那就更糟了。但是谁这样做呢,反正......)
    【解决方案7】:

    改进的 One-Liner 答案:如果您正在寻找布尔返回值,我们可以通过添加 isPresent 做得更好:

    return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();
    

    【讨论】:

    • 如果你想要一个布尔返回值,你应该使用 anyMatch
    猜你喜欢
    • 1970-01-01
    • 2012-01-21
    • 2016-03-06
    • 1970-01-01
    • 1970-01-01
    • 2013-04-30
    相关资源
    最近更新 更多