PAYATHON 2026

如何生成随机 boolean 值或双值 enum 的 Stream?

技术老齐

优先使用 Stream.generate:需要 Stream<Boolean> 时,传入 rng::nextBoolean;需要双值枚举时,就在生成函数中把 boolean 转成枚举。题目中的写法可以直接使用,直接生成枚举则能省去中间的装箱、拆箱和一个 map 阶段。

哪种方式最快,还要结合随机数生成器、JDK 和下游计算实测,代码短并不代表运行更快。

直接生成需要的类型

示例使用 Java 17 起提供的 RandomGenerator:

import java.util.random.RandomGenerator;
import java.util.stream.Stream;

enum CoinFace { HEADS, TAILS }

RandomGenerator rng = RandomGenerator.getDefault();

Stream<CoinFace> coinFlips = Stream.generate(
    () -> rng.nextBoolean() ? CoinFace.HEADS : CoinFace.TAILS
);

每生成一个元素,生成函数就调用一次 nextBoolean(),返回对应的枚举常量。每次抛硬币都会复用已有的枚举常量,不会创建新的 CoinFace 对象。

如果下游需要 Boolean,可以写成:

Stream<Boolean> booleans = Stream.generate(rng::nextBoolean);

nextBoolean() 返回基本类型 boolean,这里会自动装箱为 Boolean。装箱时会复用 Boolean.TRUE 和 Boolean.FALSE,不会为每个元素分配新对象。

这两种流都是无限、惰性生成的,直到终端操作执行时才会生成元素。实际计算时,需要限制数量,或者使用能够短路的终端操作:

coinFlips
    .limit(20)
    .forEach(System.out::println);

题目中的写法有什么区别

原来的管道:

Stream<CoinFace> coinFlips =
    Stream.generate(rng::nextBoolean)
        .map(b -> b ? CoinFace.HEADS : CoinFace.TAILS);

涉及以下类型转换:

boolean → Boolean → boolean → CoinFace

其中,装箱发生在 Stream.generate 中,拆箱发生在三元表达式的判断条件处。

把转换合并进生成函数后,过程变为:

boolean → CoinFace

这样写更直接,也减少了一个管道阶段。不过,JIT 编译器可能消除部分中间开销,实际能提速多少仍需测量。

原表达式中的方法引用:

RandomGenerator.getDefault()::nextBoolean

在创建时只会调用一次 getDefault(),后续生成元素时都使用同一个生成器。把生成器保存为变量,主要是为了方便复用和配置。

也可以用 IntStream 表示硬币结果

如果后续计算只需要区分两种结果,可以用 0 和 1 表示,让管道继续使用基本类型:

import java.util.stream.IntStream;

IntStream flips = rng.ints(0, 2);

ints(0, 2) 的范围包含 0,不包含 2,所以每个元素只能是 0 或 1。例如,约定 1 表示正面,就可以直接统计正面次数:

long heads = rng.ints(0, 2)
    .limit(1_000_000_000L)
    .filter(value -> value == 1)
    .count();

需要枚举时再转换:

Stream<CoinFace> coinFlips = rng.ints(0, 2)
    .mapToObj(value ->
        value == 1 ? CoinFace.HEADS : CoinFace.TAILS
    );

这种写法可以让后续数值运算留在 IntStream 中。如果最终仍要转换成枚举,它未必比直接调用 nextBoolean() 更快。

随机性和性能需要分别考虑

nextBoolean() 的设计语义是让 true 和 false 近似等概率出现,适合模拟普通的伪随机抛硬币。映射成 HEADS、TAILS 不会改变这一分布。有限次实验中,两种结果出现的次数有差异是正常现象。

对于十亿次操作,应测量完整计算过程的耗时。题目中的管道除了生成随机值,还会更新游程状态、进行 Integer 装箱,并逐次构造 RunningTotal。这些步骤都可能影响耗时,只优化流的源头,收益未必明显。

如果最关心吞吐量,可以用 JMH 比较直接生成枚举、IntStream 和普通循环,比较时保持随机数算法、样本量和下游计算一致。普通循环可以直接更新基本类型变量,适合作为性能基线。

用于游程计算时的注意事项

题目中的游程统计依赖硬币结果的处理次序,应保持顺序执行。Stream.generate 创建的是顺序、无序流,这里的“无序”表示没有遭遇顺序保证。不要为了提速直接添加 .parallel(),也不要假定任意 RandomGenerator 实现都能安全地被多个线程共享。

达到 .limit(...) 指定的次数时,最后一段游程可能还没有满足 heads > tails。当前 gatherer 会丢弃这段未完成的数据,采用的估算方法需要与这一统计口径保持一致。

对于现有的 Gatherer<CoinFace, ?, Integer>,直接生成 CoinFace 最容易接入。如果准备同时调整下游计算,再考虑全程使用基本类型。

备注:内容仅供参考。