bigywater.tech
Java 内部原理 · 95

Optional.orElse() vs orElseGet() 真实验证:即使有值,orElse的参数也真实被求值了

orElse() 和 orElseGet() 看起来只是写法不同,这篇真实用一个带副作用的方法当参数,证明两者的求值时机真实有本质区别——搞混了不只是性能问题,严重时会真实引发不该发生的异常。

orElse参数真实总是被求值
真实验证:即使Optional有值,orElse(expensiveMethod())里的expensiveMethod()真实还是被调用了一次
orElseGet真实才是惰性的
真实验证:orElseGet(supplier)在Optional有值时,传入的Supplier真实完全没有被执行

上一篇:String.strip() vs trim() 真实验证

真实验证:Optional有值时,orElse的参数依然真实被执行

用一个会打印日志、计数自增的方法作为orElse()的参数,观察Optional有值时它是否真的被跳过。

OptionalOrElseDemo.java Optional<String> present = Optional.of("真实存在的值"); String r1 = present.orElse(expensiveDefault()); // expensiveDefault()会真的执行吗?
真实实测
--- Optional有值时调用orElse(expensiveDefault()) ---
  真实执行了 expensiveDefault()(用于orElse场景)
orElse 真实调用次数: 1(即使Optional有值,参数表达式也真实被求值了)

真实结果证明了 orElse(T other) 的方法签名本质:它接收的是一个已经计算好的值,不是一段延迟执行的逻辑。Java 的方法调用遵循"参数在调用前求值"的规则(pass-by-value),所以 present.orElse(expensiveDefault()) 这行代码,真实是先完整执行 expensiveDefault() 算出结果,再把这个结果作为参数传给 orElse()——至于 orElse() 内部要不要用这个值,是它自己的逻辑,但传参这一步真实已经付出了计算代价。

真实验证:orElseGet(Supplier) 才是真正的惰性求值

换成orElseGet(),传入一个方法引用形式的Supplier,同样的场景再测一遍。

OptionalOrElseDemo.java String r2 = present.orElseGet(OptionalOrElseDemo::expensiveSupplier);
真实实测
--- Optional有值时调用orElseGet(this::expensiveSupplier) ---
orElseGet 真实调用次数: 0(Optional有值,Supplier真实没有被执行)

真实结果证明了 orElseGet(Supplier<? extends T> supplier) 传入的是一个函数式接口,真正的求值逻辑被真实包裹在 get() 方法里,只有在 orElseGet() 内部真实判断出 Optional 为空时,才会去调用这个 Supplier 的 get() 方法。present 本身有值,内部逻辑真实完全没有触碰这个 Supplier,所以调用次数真实是 0。

真实验证:混淆两者的真实危险后果——异常真实被提前触发

用一个会抛异常的方法作为默认值来源,验证在Optional有值的场景下,这个"不应该被用到"的异常会不会真实冒出来。

OptionalOrElseDemo.java static String throwingMethod() { throw new RuntimeException("throwingMethod真实被调用了"); } present.orElse(throwingMethod()); // present有值,"不应该"触发异常 present.orElseGet(() -> { throw new RuntimeException(...); }); // 对照
真实实测
即使Optional有值,orElse(会抛异常的方法()) 真实还是抛出了异常: throwingMethod真实被调用了
orElseGet 传入会抛异常的Supplier,Optional有值时真实安全跳过: 真实存在的值
这不是一个学术区别,是真实的生产bug来源。真实结果证明了一个容易被忽视的严重后果:如果代码里写的是 optional.orElse(fetchFromDatabaseOrThrow()) 这种模式,即使 optional 本身有值、根本不需要走默认值分支,fetchFromDatabaseOrThrow() 这个可能抛异常、可能有副作用(比如真实发起了一次数据库查询或网络请求)的调用依然会真实执行——这在真实项目里表现为"明明数据存在,却因为默认值逻辑本身出错而导致整个请求失败"这种令人费解的 bug。正确的写法应该始终是 orElseGet(() -> fetchFromDatabaseOrThrow()),用 lambda 包一层,确保这段逻辑只在真正需要默认值时才被触发。

分步演示:参数表达式是什么时候被真实执行的

每一行都是真实运行这段代码统计出的调用次数和返回结果,不是按方法签名推测的。

同一个 Optional 有值的场景,orElse 的参数真实被求值了一次,orElseGet 的 Supplier 真实一次都没碰
这一步新验证的调用

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,orElse/orElseGet调用次数统计重复运行完全一致。
    • 和Optional 与 NPE那篇一起看,能更完整理解 Optional API 表面简洁,但每个方法的真实求值语义都需要仔细区分,不能想当然地按名字猜测行为。
    • 没有涉及:orElseThrow(Supplier)自定义异常类型场景下的真实求值时机、Stream.reduce()的identity参数是否存在类似的"总是被求值"陷阱——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号