Java Streams 的内部实现是怎样的

问题描述 投票:0回答:3
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Cherry");
list.add("Peach");

List<String> collect = list.stream()
        .peek(x -> System.out.println(x))
        .filter(x -> x.length()>4)
        .peek(x -> System.out.println(x))
        .collect(Collectors.toList());

这将打印:

Apple
Apple
Cherry
Cherry
Peach
Peach

所以我的结论是:

流的第一个输入将被处理,直到流结束。 只有这样,流的第二个输入才会被处理,直到流结束。

这与普通的方法调用完全不同。通常您会期望所有输入都将在一个方法调用中得到处理。如果你调用 peek() 我希望我会看到

Apple
Cherry
Peach

但事实并非如此。

因此,stream() 的实现将是它内部有一个 for 循环,并仅使用单个输入连续调用以下方法链。

java java-stream
3个回答
3
投票

您所看到的是

peek
所描述的方式。参见(强调我的):

返回由该流的元素组成的流,另外在每个元素上执行提供的操作因为元素从结果流中被消耗

因此,您注册的查看操作(或您的情况下的操作)将在元素被消耗时执行。


3
投票

流是专门为延迟评估而构建的。换句话说:流尝试执行尽可能少的操作来获得终端操作的所需结果(如果您不执行任何终端操作,那么流通常根本不会执行任何操作)。

这在 Stream

 的 JavaDoc 中有明确记录 

流是惰性的;仅在启动终端操作时才对源数据进行计算,并且仅在需要时消耗源元素。

终端操作是包含文本“这是终端操作”的操作。


0
投票

我将根据问题在这里写另一个例子:

var list = List.of("Apple", "Cherry", "Peach");

var outputList = list.stream()
    .peek(x -> System.out.println(x))
    .filter(x -> x.length()>5)
    .peek(x -> System.out.println(x))
    .collect(Collectors.toList());

System.out.println("\n"+outputList);

这将打印

Apple
Cherry
Cherry
Peach

[Cherry]

你得到这个是因为流定义了一个由后续中间操作(在本例中是 peek、filter 和 peek)和最终终端操作(在本例中是收集)组成的管道;当执行终端操作时,源中的每个对象都会通过管道,一个接一个地传递。

在这种情况下,例如,您将有

"Apple"  => peek(print "Apple")  => filter(is "Apple" longer than 5 chars)  ? NO, stop

"Cherry" => peek(print "Cherry") => filter(is "Cherry" longer than 5 chars) ? YES, continue => peek(print "Cherry") => collect(add to the output list)

"Peach"  => peek(print "Peach")  => filter(is "Peach" longer than 5 chars)  ? NO, stop
© www.soinside.com 2019 - 2024. All rights reserved.