为什么Throwable.fillInStackTrace()方法公开?为什么会有人使用它?

时间:2012-03-20 14:45:44

标签: java exception

我很好奇为什么fillInStackTrace的方法java.lang.Throwable是公开的?

此方法将原始堆栈跟踪替换为调用它的位置,删除本地化异常所需的信息。它可以用于混淆,但不需要太多努力,因为新的堆栈跟踪将指向混淆代码。更好的方法是简单地隐藏异常或抛出新异常。

但我无法找到在现有Throwable上调用此方法的任何合理案例。所以问题是:为什么这种方法是公开的?背后有什么意义吗?

6 个答案:

答案 0 :(得分:44)

一个原因是表现。投掷和捕捉异常很便宜;昂贵的部分是filling in the stack trace。如果你覆盖fillInStackTrace()什么都不做,那么创建例外也会变得很便宜。

除了便宜的例外,你可以使用flow control的例外,这可以使代码在某些情况下更具可读性;在实现需要more advanced flow control的JVM语言时,可以使用它们,如果编写actors library,它们非常有用。

答案 1 :(得分:20)

一种可能合法的用法是在与实际投掷的地方不同的地方创建例外。例如,您可能有一个应用程序,您可以在其中提供用于生成自定义异常的插件工具。由于堆栈跟踪填充在 on construction 上,因此异常的跟踪将包括可能误导的信息(它将包括对自定义异常工厂的调用)。因此,您的应用程序将在自定义工厂生成后的异常上调用fillInStackTrace(),以便为异常的最终接收者提供更准确的堆栈跟踪。

这个rejected bug表示你并不是唯一一个对此方法需要困惑的人。

答案 2 :(得分:5)

Throwable.fillInStackTrace()的一个合法用例是非本地控制流:

例如,在我的TrueZIP框架内,我使用复杂的装饰器链来处理文件系统控制器。该链中的一个控制器是类FsLockController的一个实例。该对象负责管理文件系统的ReentrantReadWriteLock。现在,此链中更深处的文件系统控制器可能检测到需要写锁定,但FsLockController仅获取了读锁定。因为在没有死锁的情况下无法将读锁升级为写锁,所以必须抛出异常,这恰好是类FsNeedsWriteLockException的一个实例。然后,装饰FsLockController将捕获此异常,释放读锁并获取写锁,然后再次重试该操作。

这种非本地控制流程实际上非常有效,但有一点需要考虑:抛出和捕获异常很便宜,但填写或检查其堆栈跟踪是很昂贵的。现在因为这个特殊的异常类型是在这个文件系统控制器链中单独抛出和捕获的,所以我根本不需要堆栈跟踪,因此我可以安全地使用空方法体覆盖Throwable.fillInStackTrace()以抑制这种昂贵的操作

可以看到此异常类型的基类的源代码here

答案 3 :(得分:3)

以下是一个合理的案例:隐藏未经授权用户的应用程序详细信息。例如,您不希望在许可证密钥过期时公开抛出的异常堆栈跟踪,因为这样可以简化黑客的工作。

答案 4 :(得分:1)

类似的question提供了答案。

根据JLS,默认情况下,在实例化异常时填充堆栈跟踪。但是,有时您可能希望在重新抛出时将堆栈“重置”到更有用的位置。

要允许此功能,我们有fillInStackTrace()

修改

好吧,今天我学习了Java标准库规范在修订版1之后从JLS中删除了。正如第二版的前言所述:

The specifications of the libraries are now far too large to fit into this volume, and
they continue to evolve. Consequently, API specifications have been removed from
this book. The library specifications can be found on the Web; this specification
now concentrates solely on the Java programming language proper.

因此,我们确实需要更仔细地查看javadoc来解决这个问题。这里的API非常宽松。整个相关部分来自标题,其中指出:

Instances of two subclasses, Error and Exception, are conventionally used to indicate   
that exceptional situations have occurred. Typically, these instances are freshly 
created in the context of the exceptional situation so as to include relevant 
information (such as stack trace data).

java.lang.Throwable.printStackTrace()是

Prints this throwable and its backtrace to the standard error stream.

我认为这是允许VM实现者为其上下文提供最高性能/适当的实现。有了这个上下文,公开显示fillInStackTrace()会更有意义。

默认情况下,虚拟机会为您提供尽力回溯位置。使fillInStackTrace()公开允许开发人员进一步改进。

-

最后一次编辑:)

我设法找到了relevant bits of the JLS First Edition的剩余链接,我想提供一个进一步说明该方法公开的原因。无论是好是坏还是Java中的常见解释。

总是这样。

答案 5 :(得分:1)

也许你想捕获一个异常并抛出一个没有封装你以前异常的新异常。来自捕获的getStackTrace,并为新的设置stackTrace。

当你有一个单例异常时,这也会有效,例如:

实例化一次
private static Exception theException = new MyException();

被抛出多次。这会将stackTrack设置为与捕获的异常相关的内容,而无需每次都昂贵地创建新的异常。我认为这就是Scala继续并打破构造的方式。

try {
    ....
} catch (OtherException oe) {
    theException.fillStackTrace(oe.getStackTrace());
    theException.setMessage(...); //etc
    throw theException;
}

如果可以同时抛出多次(即在线程中),使用静态异常可能会很棘手