如何利用 Stream.iterate() 函数构建斐波那契数列数据流?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1069个文字,预计阅读时间需要5分钟。
关键在于理解 `Stream.iterate(seed, UnaryOperator)` 的签名:
常见错误是试图传入两个独立的初始值,或者把 f 写成只更新单个数字的逻辑,结果流里全是重复值或越界异常。
- 用数组作为状态:种子是
new long[]{0L, 1L},函数返回new long[]{pair[1], pair[0] + pair[1]} - 用 record 更清晰(Java 14+):
record Fib(long a, long b) {},种子为new Fib(0L, 1L),函数返回new Fib(b, a + b) - 别在
f中做边界判断(比如if (a > MAX) break)——iterate()不支持中途终止,得靠后续的limit()或takeWhile()
如何提取每项的“当前斐波那契值”
流中每个元素是状态对象(比如 Fib 或 long[]),但你通常只想要当前项的值(即 a),不是整个状态。必须用 map() 抽取:
Stream.iterate(new Fib(0L, 1L), f -> new Fib(f.b, f.a + f.b)) .map(f -> f.a) .limit(10) .forEach(System.out::println);
如果漏掉 map(),输出的是 Fib{a=0, b=1}、Fib{a=1, b=1} 这类对象,不是数字序列。
- 用数组时同理:
.map(pair -> pair[0]) - 注意
map()必须在limit()前——否则会先无限生成状态再截断,可能触发 OOM 或卡死 - 若想输出
0, 1, 1, 2, 3...,种子必须是Fib(0, 1);若误设为Fib(1, 1),首项就丢掉了 0
为什么不用 Stream.generate() 或递归 Stream.concat()
Stream.generate() 没有状态传递机制,每次调用 supplier 都是全新上下文,无法记住上一项;而手写递归拼接(比如 Stream.concat(Stream.of(0), Stream.concat(...)))会在构造阶段就展开整个链表,时间/空间复杂度都是 O(n),且极易栈溢出。
iterate() 是惰性求值的,只有在终端操作触发时才按需计算下一项,内存占用恒定(只存当前状态),这才是正确姿势。
- 性能差异明显:生成前 100 万项时,
iterate()耗时稳定在毫秒级;递归拼接在几万项就抛StackOverflowError -
generate()只能靠外部变量(如AtomicLong)模拟状态,破坏函数式纯洁性,且多线程不安全 - 某些旧教程用
Stream.iterate(0L, n -> n + 1).map(Fibonacci::nth),这是伪惰性——nth()每次都从头算,O(n²) 复杂度,绝对要避开
边界与溢出处理不能靠 iterate() 自身解决
Stream.iterate() 本身不检查数值溢出。当 long 超过 Long.MAX_VALUE(第 92 项左右),后续值会变成负数甚至乱序,但流不会报错或停止。
真正安全的做法是在 map() 后加 takeWhile() 判断是否仍为正数,或改用 BigInteger:
Stream.iterate(new BigInteger[]{BigInteger.ZERO, BigInteger.ONE}, p -> new BigInteger[]{p[1], p[0].add(p[1])}) .map(p -> p[0]) .limit(100) .forEach(System.out::println);
用 BigInteger 时记得改用 new BigInteger[]{...} 作种子,并在 map 中取 [0],否则类型不匹配编译不过。
- 别指望
iterate()的UnaryOperator里 throw Exception 来中断流——这会导致RuntimeException,不是优雅终止 - 如果业务只要前 N 项,
limit(N)最简单;如果要“直到某值为止”,必须用takeWhile(x -> x.compareTo(MAX) - record 方式在 Java 14+ 可读性好,但若项目还在用 Java 8,老实用
long[]或写个轻量 inner class
本文共计1069个文字,预计阅读时间需要5分钟。
关键在于理解 `Stream.iterate(seed, UnaryOperator)` 的签名:
常见错误是试图传入两个独立的初始值,或者把 f 写成只更新单个数字的逻辑,结果流里全是重复值或越界异常。
- 用数组作为状态:种子是
new long[]{0L, 1L},函数返回new long[]{pair[1], pair[0] + pair[1]} - 用 record 更清晰(Java 14+):
record Fib(long a, long b) {},种子为new Fib(0L, 1L),函数返回new Fib(b, a + b) - 别在
f中做边界判断(比如if (a > MAX) break)——iterate()不支持中途终止,得靠后续的limit()或takeWhile()
如何提取每项的“当前斐波那契值”
流中每个元素是状态对象(比如 Fib 或 long[]),但你通常只想要当前项的值(即 a),不是整个状态。必须用 map() 抽取:
Stream.iterate(new Fib(0L, 1L), f -> new Fib(f.b, f.a + f.b)) .map(f -> f.a) .limit(10) .forEach(System.out::println);
如果漏掉 map(),输出的是 Fib{a=0, b=1}、Fib{a=1, b=1} 这类对象,不是数字序列。
- 用数组时同理:
.map(pair -> pair[0]) - 注意
map()必须在limit()前——否则会先无限生成状态再截断,可能触发 OOM 或卡死 - 若想输出
0, 1, 1, 2, 3...,种子必须是Fib(0, 1);若误设为Fib(1, 1),首项就丢掉了 0
为什么不用 Stream.generate() 或递归 Stream.concat()
Stream.generate() 没有状态传递机制,每次调用 supplier 都是全新上下文,无法记住上一项;而手写递归拼接(比如 Stream.concat(Stream.of(0), Stream.concat(...)))会在构造阶段就展开整个链表,时间/空间复杂度都是 O(n),且极易栈溢出。
iterate() 是惰性求值的,只有在终端操作触发时才按需计算下一项,内存占用恒定(只存当前状态),这才是正确姿势。
- 性能差异明显:生成前 100 万项时,
iterate()耗时稳定在毫秒级;递归拼接在几万项就抛StackOverflowError -
generate()只能靠外部变量(如AtomicLong)模拟状态,破坏函数式纯洁性,且多线程不安全 - 某些旧教程用
Stream.iterate(0L, n -> n + 1).map(Fibonacci::nth),这是伪惰性——nth()每次都从头算,O(n²) 复杂度,绝对要避开
边界与溢出处理不能靠 iterate() 自身解决
Stream.iterate() 本身不检查数值溢出。当 long 超过 Long.MAX_VALUE(第 92 项左右),后续值会变成负数甚至乱序,但流不会报错或停止。
真正安全的做法是在 map() 后加 takeWhile() 判断是否仍为正数,或改用 BigInteger:
Stream.iterate(new BigInteger[]{BigInteger.ZERO, BigInteger.ONE}, p -> new BigInteger[]{p[1], p[0].add(p[1])}) .map(p -> p[0]) .limit(100) .forEach(System.out::println);
用 BigInteger 时记得改用 new BigInteger[]{...} 作种子,并在 map 中取 [0],否则类型不匹配编译不过。
- 别指望
iterate()的UnaryOperator里 throw Exception 来中断流——这会导致RuntimeException,不是优雅终止 - 如果业务只要前 N 项,
limit(N)最简单;如果要“直到某值为止”,必须用takeWhile(x -> x.compareTo(MAX) - record 方式在 Java 14+ 可读性好,但若项目还在用 Java 8,老实用
long[]或写个轻量 inner class

