orElse() 和 orElseGet() 看起来只是写法不同,这篇真实用一个带副作用的方法当参数,证明两者的求值时机真实有本质区别——搞混了不只是性能问题,严重时会真实引发不该发生的异常。
上一篇:String.strip() vs trim() 真实验证
用一个会打印日志、计数自增的方法作为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,同样的场景再测一遍。
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有值时真实安全跳过: 真实存在的值
每一行都是真实运行这段代码统计出的调用次数和返回结果,不是按方法签名推测的。