在Java中始终抛出相同的exception实例

总是告诉我Javaexception处理非常昂贵。

我问在程序开头创建特定类型的exception实例是否是一个好习惯,而不创建新的exception实例,总是抛出相同的exception对象。

我只想举个例子。 常用代码:

if (!checkSomething(myObject)) throw new CustomException("your object is invalid"); 

替代方案:

 static CustomException MYEXP = new CustomException("your object is invalid"); //somewhere else if (!checkSomething(myObject)) throw MYEXP; 

当然,我在这里做一些假设:

  1. MyCustomException没有参数
  2. 客户端代码,无论何时是一种良好的实践,都是基于exception处理的重量级,并且重构不是一种选择。

所以问题是:

  1. 这是一个好习惯吗?
  2. 这会损害一些JVM机制吗?
  3. 如果1为是,则有可能获得性能提升? (我想不是,但不确定)
  4. 如果1和3是肯定的,为什么不赞助作为练习?
  5. 如果1不是,为什么Martin Odersky在他对Scala的介绍中告诉我这是Scala在某些情况下的工作原理? (在28:30分,他告诉说实施rest已经抛出exception,观众说这很费时间并且他回复说每次都不会创建例外) Fosdem 2009

我希望这不是一个闲置/愚蠢的问题,我对此很好奇。 我认为exception处理的实际成本是处理而不是创建。

编辑添加了关于FOSDEM演示的精确讨论的参考

免责声明:我的代码没有像提议的那样工作,我无意管理这样的exception,我只是在做一个“假设”问题,而这种好奇心是从video的肯定中产生的。 我想:如果在Scala中完成,为什么不在Java中呢?

不,不要那样做。 昂贵的部分不处理exception,它正在生成堆栈跟踪。 不幸的是,堆栈跟踪也是有用的部分。 如果你抛出一个保存的exception,你将传递一个误导性的堆栈跟踪。

可能是在Scala的实现中,有些情况下执行此操作是有意义的。 (也许他们正在做一些递归的事情,并且想要预先生成一个exception对象,所以如果他们的内存耗尽,他们仍然会产生exception。)他们也有很多关于他们正在做什么的信息,所以他们有更好的机会做对了 但是JVM语言实现者所做的优化是一个非常特殊的情况。

因此,除非您认为提供误导性信息构成破损,否则您不会破坏任何东西。 这对我来说似乎是一个很大的风险。

尝试Thomas Eding关于如何在没有堆栈跟踪的情况下创建exception的建议似乎有效:

 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 :

由方法blowUp抛出的NullPointerException(它是一种RuntimeException)不会被main中的try语句捕获,因为NullPointerException不能分配给BlewIt类型的变量。 这会导致finally子句执行,之后执行main的线程(测试程序的唯一线程)因未捕获的exception而终止,这通常会导致打印exception名称和简单的回溯。 但是,本规范不要求回溯。

强制回溯的问题在于,可以在程序中的某一点创建exception,然后抛出exception。 将堆栈跟踪存储在exception中是非常昂贵的,除非它实际被抛出(在这种情况下,可以在展开堆栈时生成跟踪)。 因此,我们不会在每个例外中强制要求回溯。

Q1。 这是一个好习惯吗?

不在我的书中。 它增加了复杂性并阻碍了诊断(参见我对Q2的回答)。

Q2。 这会损害一些JVM机制吗?

您不会从这样的exception对象获得有意义的堆栈跟踪。

Q3。 如果1是,那么有表演获益吗? (我想不是,但不确定)

Q4。 如果1和3是肯定的,为什么不赞助作为练习?

由于上面列出的问题。

Q5。 如果1不是,为什么Martin Odersky在他对Scala的介绍中告诉我这是Scala在某些情况下的工作原理? (对不起,但我现在还记不起这种肯定的背景)Fosdem 2009

没有上下文很难回答。

你可以这样做,但例外

  1. 必须没有堆栈跟踪,因为初始堆栈跟踪只会在后续使用中混淆。

  2. 不得接受被抑制的例外。 如果多个线程试图向其添加抑制的exception,那么事情就会破坏。

所以你的exception构造函数必须这样做

 super(msg, cause, /*enableSuppression*/false, /*writableStackTrace*/false); 

请参阅http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#Throwable%28java.lang.String,%20java.lang.Throwable,%20boolean,%20boolean%29


现在,它有用吗? 是的,否则为什么这两个布尔标志首先存在?:)

在一些复杂的情况下,exception可以用作流控制设备,它可以产生更简单,更快的代码。 这种例外被称为“控制例外”。

如果exception确实表明程序存在exception错误,则使用传统exception。

虽然例外是相对昂贵的并且应该保持在最低限度,但是它们不会花费太多以至于你应该为了性能目的而做一些迟钝的事情。这通常是一个不好的借口,有些人甚至认为过早优化应该不惜一切代价避免。 虽然这不完全正确,但您可以衡量exception的缓慢程度。

 long start = System.nanoTime(); int exceptionCount = 0; for (int i = 0; i < 20000; i++) try { int j = i / (i & 1); } catch (ArithmeticException ae) { exceptionCount++; } long time = System.nanoTime() - start; System.out.printf("Each exception took average of %,d ns%n", time / exceptionCount); 

打印出我认为合理估计的内容。

 Each exception took average of 3,064 ns 

注意:随着循环次数的增加,Exception将被优化掉。 即迭代次数为10次

 Each exception took average of 327 ns 

并且还要多出10倍

 Each exception took average of 35 ns 

并且还要多出10倍

 Each exception took average of 5 ns 

如果exception被抛出,JIT似乎足够聪明,可以优化Exception。