构造异常实例是否比构造异常Supplier实例的性能开销更高?
Java Optional中orElseThrow写法问题解答
首先给出两种写法的代码对照:
// 正确写法 var value = getOptional().orElseThrow(NullPointerException::new); // 错误写法,无法通过编译 var value = getOptional().orElseThrow(new NullPointerException());
为什么必须使用传入方法引用的写法?
1. 语法层面的硬性要求
Optional.orElseThrow 从Java 8首次引入开始,就只提供两种重载:
- 无参版本:空值时直接抛出
NoSuchElementException - 单参版本:仅接收
Supplier<? extends Throwable>类型的参数,用来自定义抛出的异常类型
不存在直接接收异常实例作为参数的重载,因此第二种写法本身就不符合Java语法规范,编译阶段就会报错,根本无法运行。
2. 延迟加载的性能合理性
即使不考虑语法问题,直接传入异常实例的逻辑也存在严重的性能浪费:
直接写new NullPointerException()时,异常实例会在orElseThrow方法调用前就被创建完成,不管getOptional()返回的Optional是否有值,这个创建过程的开销都会被触发。
而传入NullPointerException::new这个Supplier实现时,只有当Optional确实为空、需要抛出异常的时候,才会调用Supplier的get()方法创建异常实例,Optional有值的场景下完全不会产生额外开销,这个逻辑和orElse、orElseGet的差异是完全一致的。
创建异常实例的开销是否比Supplier实例更高?
答案是前者开销远高于后者,核心差异来自两者的创建逻辑:
- 异常实例创建时,JVM会自动生成当前线程的完整栈追踪信息,需要遍历整个调用栈采集类名、方法名、行号等全量栈帧数据,是非常重的操作,哪怕创建后不抛出,开销也已经产生。
- 而
NullPointerException::new属于无上下文捕获的方法引用,对应的Supplier是无状态的轻量函数对象,JVM可以对其做逃逸分析、实例缓存等多层优化,极端场景下甚至可以完全消除对象创建的开销,整体开销和异常创建相比几乎可以忽略。
内容的提问来源于stack exchange,提问作者Bogdan Tischenko
相关产品推荐
相关产品推荐

