Java 8流 - stackoverflow异常

slo*_*kps 11 java reduce java-8 java-stream

运行以下代码示例以:
"线程中的异常"main"java.lang.StackOverflowError"结束

import java.util.stream.IntStream;
import java.util.stream.Stream;

public class TestStream {

    public static void main(String[] args) {
        Stream<String> reducedStream = IntStream.range(0, 15000)
            .mapToObj(Abc::new)
            .reduce(
                Stream.of("Test")
                , (str , abc) -> abc.process(str)
                , (a , b) -> {throw new IllegalStateException();}
        );
        System.out.println(reducedStream.findFirst().get());
    }

    private static class Abc { 
        public Abc(int id) {
        }

        public Stream<String> process(Stream<String> batch) {
            return batch.map(this::doNothing);
        }

        private String doNothing(String test) {
            return test;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

究竟是什么导致了这个问题?这段代码的哪一部分是递归的,为什么?

Ger*_*cke 4

您的代码不是递归循环。您可以使用较小的数字来测试 IntStream 范围(即 1 或 100)。在您的情况下,实际的堆栈大小限制导致了问题。正如一些评论中指出的那样,这是流的处理方式。

对流的每次调用都会在原始流周围创建一个新的包装流。“findFirst()”方法向前一个流请求元素,后者又向前一个流请求元素。因为流不是真正的容器,而只是结果元素上的指针。

包装器爆炸发生在reduce 方法的累加器'(str , abc) -> abc.process(str)' 中。该方法的实现在前一个操作的结果 (str) 上创建一个新的流包装器,输入到下一个迭代中,在结果上创建一个新的包装器(result(str)))。因此,累积机制是包装器(递归)之一,而不是附加器(迭代)之一。因此,创建实际(扁平)结果的新流而不是参考潜在结果将阻止爆炸,即

public Stream<String> process(Stream<String> batch) {
        return Stream.of(batch.map(this::doNothing).collect(Collectors.joining()));
    }
Run Code Online (Sandbox Code Playgroud)

此方法只是一个示例,因为您原来的示例没有任何意义,因为它什么也不做,这个示例也没有任何意义。这只是一个例子。它基本上将 map 方法返回的流的元素展平为单个字符串,并在此具体字符串上创建一个新流,而不是在流本身上,这就是与原始代码的区别。

您可以使用“-Xss”参数调整堆栈大小,该参数定义每个线程的堆栈大小。默认值取决于平台,另请参阅这个问题“Java 调用堆栈的最大深度是多少?” 但增加时要小心,该设置适用于所有线程。