通过谓词查找第一个元素


504

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

例如,大多数功能语言都具有某种对序列进行操作的find函数,或者对返回谓词为的第一个元素的列表进行操作true。我看到的在Java 8中实现此目标的唯一方法是:

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

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


53
它不是低效的,Java 8 Stream实现是延迟评估的,因此过滤器仅应用于终端操作。同样的问题在这里:stackoverflow.com/questions/21219667/stream-and-lazy-evaluation
Marek Gregor 2014年

1
凉。那就是我希望的。否则,它将是一个主要的设计失败。
斯基

2
如果您的目的实际上是检查列表中是否包含这样的元素(不单挑其中的第一个元素),则.findAny()在并行设置中理论上可以更有效,并且当然可以更清楚地传达此意图。
Joachim Lous

与简单的forEach周期相比,这将在堆上创建许多对象以及数十个动态方法调用。尽管这可能并不总是影响性能测试的底线,但在热点地区,请避免使用琐碎的Stream和类似的重量级结构。
Agoston Horvath

Answers:


720

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

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

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

因此,您可以采用完全正确的方法。


37
需要注意的是,我get();在这里使用它是因为我知道我将哪些值馈送到流管道,因此会有结果。实际上,您不应使用get();,而应使用orElse()// orElseGet()/ orElseThrow()(对于更有意义的错误,而不是NSEE),因为您可能不知道应用于流管道的操作是否会导致元素。
Alexis C.

31
.findFirst().orElse(null);例如
Gondy '16

20
不要使用orElse null。那应该是一种反模式。所有这些都包含在“可选”中,为什么还要冒险使用NPE?我认为处理Optional是更好的方法。在使用它之前,只需使用isPresent()测试Optional。
BeJay

@BeJay,我听不懂。我应该用什么代替orElse
约翰·亨克尔

3
@JohnHenckel我认为BeJay的意思是您应该将其保留为一种Optional类型,这就是.findFirst返回值。Optional的用途之一是帮助开发人员避免处理nullseg而不是check myObject != null,您可以选中myOptional.isPresent()或使用Optional接口的其他部分。这样更清楚了吗?
AMTerp

102

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

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

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


5
这个答案对我来说更有用,它解释了原因,而不仅仅是方式。我从来没有新的中间操作总是很懒惰。Java流继续让我感到惊讶。
kevinarpe

30
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;
shreedhar bhat

1
@shreedharbhat您不需要这样做.orElse(null) != null。而是使用Optional API,.isPresent.findFirst().isPresent()
AMTerp

@shreedharbhat首先,OP并不是在寻找布尔返回值。其次,如果这样的话,写起来会更干净.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id))
AjaxLeung

13

除了Alexis C的答案以外,如果您正在使用数组列表(不确定要搜索的元素是否存在)中,请使用此列表。

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

然后,您可以简单地检查a是否为null


1
您应该修复您的示例。您不能将null分配给普通int。stackoverflow.com/questions/2254435/can-an-int-be-null-in-java
RubioRic

我已经编辑了你的帖子。在整数列表中搜索时,0(零)可能是有效的结果。将变量类型替换为整数,将默认值替换为null。
RubioRic

0

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

0

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

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

如果您想要布尔值,则应该使用anyMatch
AjaxLeung
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.