在Java中,总是抛出相同的异常实例。
在Java中,总是抛出相同的异常实例。
有人告诉我Java的异常处理非常昂贵。我想知道在程序开始时创建一个特定类型的异常实例,并且不创建新的异常对象,始终抛出相同的异常对象,这样做是否是一个好的实践。我只是想举个例子。常规代码:\n
if (!checkSomething(myObject)) throw new CustomException("您的对象无效");
\n替代方案:\n
static CustomException MYEXP = new CustomException("您的对象无效"); //其他地方 if (!checkSomething(myObject)) throw MYEXP;
\n当然,我在这里做了一些假设:\n
- \n
MyCustomException
没有参数- 客户端代码,无论是不是一个好的实践,都严重依赖异常处理,重构不是一个选项。
\n
\n
\n所以问题是:\n
- \n
- 这是一个好的实践吗?
- 这会损害JVM的某些机制吗?
- 如果问题1的答案是肯定的,是否有可能提高性能?(我认为不会,但不确定)
- 如果问题1和问题3的答案都是肯定的,为什么它没有被提倡为一种实践?
- 如果问题1的答案是否定的,为什么Martin Odersky在他对Scala的介绍中说这是某些情况下Scala的工作方式?(在28分30秒的地方,他说break是通过抛出异常来实现的,听众说这会消耗时间,他回答说异常不是每次都创建)Fosdem 2009
\n
\n
\n
\n
\n
\n我希望这不是一个无聊/愚蠢的问题,我对此很好奇。我认为异常处理的真正成本是处理而不是创建。
在Java中,抛出总是相同的异常实例是可能的。但是,为了避免在后续使用中引起混淆,该异常必须没有堆栈跟踪,并且不能接受被抑制的异常。如果多个线程尝试向其添加被抑制的异常,会导致数据损坏。
因此,异常的构造函数必须使用以下方式:
super(msg, cause, /*enableSuppression*/false, /*writableStackTrace*/false);
这样做有什么用呢?这两个boolean标志为什么会存在呢?在一些复杂的情况下,异常可以用作流程控制设备,它可以产生更简单、更快速的代码。这些异常被称为"控制异常"。如果异常确实表示程序出现异常问题,那么应该使用传统的异常。
感谢您展示了"控制异常"的想法并不是那么疯狂。
在Java中,"Throwing always the same exception instance"问题指的是在异常处理中反复使用同一个异常实例。这种做法不被推荐,因为它增加了复杂性并且阻碍了诊断。
这种做法会破坏JVM机制,因为无法从这样的异常对象中获取有意义的堆栈跟踪。
虽然这种做法可能会带来性能上的提升,但由于上述问题的存在,它并没有被作为一种推荐的做法。
关于Martin Odersky在Scala介绍中提到的这种做法,没有上下文的情况下很难回答。
"Throwing always the same exception instance"问题不被推荐,因为它增加了复杂性、阻碍了诊断,并且可能破坏JVM机制。尽管这种做法可能带来性能上的提升,但由于问题的存在,它并没有被作为一种推荐的做法。
在Java中,抛出总是相同的异常实例是一个不好的做法。这是因为,昂贵的部分不是处理异常,而是生成堆栈跟踪。不幸的是,堆栈跟踪也是有用的部分。如果抛出保存的异常,您将传递一个误导性的堆栈跟踪。
Scala的实现中可能存在一些情况,这样做是有意义的。(也许他们正在递归地做某些事情,并且希望提前生成异常对象,以便在内存不足时仍然可以产生异常。)他们对自己正在做的事情有很多信息,所以他们有更好的机会做到正确。但是,JVM语言实现者所做的优化是一个非常特殊的情况。
所以,除非您认为提供误导性信息构成破坏,否则您不会破坏任何东西。对我来说,这似乎是一个很大的风险。
尝试一下Thomas Eding的建议,如何创建一个没有堆栈跟踪的异常似乎可以工作:
groovy:000> class MyException extends Exception { groovy:001> public Throwable fillInStackTrace() {}} ===> true groovy:000> e = new MyException() ===> MyException groovy:000> Arrays.asList(e.stackTrace) ===> []
还可以查看JLS(Java语言规范)的相关部分:
> NullPointerException(它是一种RuntimeException)由blowUp方法抛出,但是在main方法的try语句中没有捕获到,因为NullPointerException不能分配给类型为BlewIt的变量。这导致finally子句执行,然后执行main方法的线程(测试程序中唯一的线程)因为未捕获的异常而终止,这通常会导致打印异常名称和简单的回溯。然而,此规范不要求回溯。
强制要求有一个回溯的问题在于,异常可能在程序的一个地方创建并在稍后的地方抛出。除非在实际抛出异常时(在解开堆栈的同时可能生成跟踪),否则将堆栈跟踪存储在异常中是代价很高的。因此,我们不在每个异常中都要求一个回溯。
但是,如果您对堆栈跟踪不感兴趣并且自己捕获异常,这可能是加快速度的一种方法。或者是否有一种方法可以创建一个没有堆栈跟踪的异常?
:我认为您无法创建没有堆栈跟踪的异常。像Ruby这样的语言有一种抛出非异常的方法。在Java中,我们只能返回null。Michael Myers的评论提到了一个很好的观点,即异常应该是异常的。
:重写fillInStackTrace会有很大的作用吗?
:我很惊讶他们把它留成了public。将其添加到答案中,谢谢。