Java 为什么可丢弃实例在实例化过程中而不是在抛出之前填写stacktrace?

Java 为什么可丢弃实例在实例化过程中而不是在抛出之前填写stacktrace?,java,exception,stack-trace,throw,Java,Exception,Stack Trace,Throw,我不确定以前是否有人问过这个问题,但我一直在想,为什么Throwable的每个实例,除非fillInStackTrace方法被覆盖/抑制,在默认情况下实例化时,都会在构造函数中填充其堆栈跟踪。 考虑下面的代码: final class ThrowableTest{ 私有ThrowableTest(){} 公共静态void main(最后一个字符串…args)抛出可丢弃的{throw go();} 私有静态可丢弃go(){return goDeep();} 私有静态可丢弃goDeep(){retu

我不确定以前是否有人问过这个问题,但我一直在想,为什么
Throwable
的每个实例,除非
fillInStackTrace
方法被覆盖/抑制,在默认情况下实例化时,都会在构造函数中填充其堆栈跟踪。 考虑下面的代码:

final class ThrowableTest{
私有ThrowableTest(){}
公共静态void main(最后一个字符串…args)抛出可丢弃的{throw go();}
私有静态可丢弃go(){return goDeep();}
私有静态可丢弃goDeep(){return goDeep();}
私有静态可丢弃goDeeper(){return goevendeper();}
私有静态可丢弃goEvenDeeper(){return new Throwable();}
}
执行上述代码将产生以下输出:

Exception in thread "main" java.lang.Throwable
    at ThrowableTest.goEvenDeeper(scratch.java:9)
    at ThrowableTest.goDeeper(scratch.java:8)
    at ThrowableTest.goDeep(scratch.java:7)
    at ThrowableTest.go(scratch.java:6)
    at ThrowableTest.main(scratch.java:5)
我会发现以下假设的stacktrace更直观(假设
fillInStackTrace
方法没有被覆盖,并且
Throwable
构造函数在这种“特殊”JRE中没有调用它):

我想知道:为什么stacktrace是在构造函数中填充的(在最简单的场景中),而不是在异常抛出站点中使用
throw
? 如果我没有弄错的话,在某些情况下,可以使用
fillInStackTrace
将异常配置为(重新)抛出,并且如上所述的
throw
将stacktrace重新写入当前异常。 但是如果是这样的话,
throw
语句(以及
athrow
指令)可能会在自己填充异常之前检查异常的stacktrace是否设置为
null
(假设抛出除
Throwable
实例之外的其他内容未定义,如规范中所述)。
它背后的设计选择是什么?

“更直观”?真正地这将使查找错误异常困难!异常并不意味着返回,而是在异常产生的地方创建…@CarlosHeuberger是的,更直观,因为它标记了异常抛出的点,而不是它实例化的点。它不关心后者,我也不知道为什么构造函数会受到
fillInStackTrace
惩罚。假设您有一个工厂方法来实例化域逻辑异常,比如
throw ObjectNotFoundException.create(SomeType.FOO,barId)
,它可以是一个将实例化委托给其他地方的方法——这就是我通过整个方法链返回异常的意思。@CarlosHeuberger,有时,异常必须扮演服务或流控制角色,在这些情况下,不填充堆栈跟踪是非常关键的——这就是为什么该方法是公共的和可重写的。对于抽象异常的构建,还有一个非常有力的论据,包括根据条件交换其类型。这意味着,虽然异常通常是从创建它们的同一行抛出的,但这不是唯一的用例。它背后的设计选择是什么,您应该向做出这些选择的人提出一个问题。但是,当对象还不存在时,在对象中“填充”内容是相对困难的,如果“填充”是由于抛出而生成的编译代码中不可避免的一部分,那么很难避免填充,这是一种完全合理且完全可行的选择。@fluffy,它可能会工作,但这会使系统复杂化,因为这实际上是一个极端情况,因为您必须跟踪在某个时刻以某种方式抛出的所有异常。所以从用户的角度来看,它们是一个一次性的对象,但是JVM必须保持它们,以防有人抓住一个并将其保存在某个地方。“更直观”?真正地这将使查找错误异常困难!异常并不意味着返回,而是在异常产生的地方创建…@CarlosHeuberger是的,更直观,因为它标记了异常抛出的点,而不是它实例化的点。它不关心后者,我也不知道为什么构造函数会受到
fillInStackTrace
惩罚。假设您有一个工厂方法来实例化域逻辑异常,比如
throw ObjectNotFoundException.create(SomeType.FOO,barId)
,它可以是一个将实例化委托给其他地方的方法——这就是我通过整个方法链返回异常的意思。@CarlosHeuberger,有时,异常必须扮演服务或流控制角色,在这些情况下,不填充堆栈跟踪是非常关键的——这就是为什么该方法是公共的和可重写的。对于抽象异常的构建,还有一个非常有力的论据,包括根据条件交换其类型。这意味着,虽然异常通常是从创建它们的同一行抛出的,但这不是唯一的用例。它背后的设计选择是什么,您应该向做出这些选择的人提出一个问题。但是,当对象还不存在时,在对象中“填充”内容是相对困难的,如果“填充”是由于抛出而生成的编译代码中不可避免的一部分,那么很难避免填充,这是一种完全合理且完全可行的选择。@fluffy,它可能会工作,但这会使系统复杂化,因为这实际上是一个极端情况,因为您必须跟踪在某个时刻以某种方式抛出的所有异常。所以从用户的角度来看,它们是一个一次性的对象,但是JVM必须保持它们,以防有人得到一个并将其保存在某个地方。
Exception in thread "main" java.lang.Throwable
    at ThrowableTest.main(scratch.java:5)