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

时间:2013-01-23 21:25:07

标签: java scala exception-handling

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

我想问一下,在程序开头创建特定类型的异常实例并且不创建新的异常实例是一个好习惯,总是抛出相同的异常对象。

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

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. 客户端代码,无论何时是一个好的做法,都是基于异常处理的重量级,并且重构不是一种选择。
  3. 所以问题是:

    1. 这是一个好习惯吗?
    2. 这是否会破坏某些JVM机制?
    3. 如果1为是,则有可能获得性能提升? (我想不是,但不确定)
    4. 如果1和3是肯定的,为什么不赞助这种做法?
    5. 如果1不是,为什么Martin Odersky在他对Scala的介绍中告诉我这是Scala在某些情况下的工作原理? (在28:30分,他告诉说实施休息已经抛出异常,观众说这很费时间并且他回复说每次都不会创建异常)Fosdem 2009
    6. 我希望这不是一个空闲/愚蠢的问题,我对此很好奇。我认为异常处理的实际成本是处理而不是创建。

      修改 增加了关于FOSDEM演示的精确讨论的参考

      免责声明:我的代码都没有像提议的那样工作,我无意管理这样的异常,我只是在做一个“假设”问题,这个好奇心来自于视频的肯定。我想:如果在Scala中完成,为什么不在Java?

4 个答案:

答案 0 :(得分:17)

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

可能在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

  

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

     

强制回溯的问题是异常可以   在程序中的某一点创建并在稍后抛出。它是   除非,在异常中存储堆栈跟踪非常昂贵   它实际上被抛出(在这种情况下,跟踪可能会生成   解开堆栈)。因此,我们不会强制要求每一个都有回溯   异常。

答案 1 :(得分:3)

  

Q1。这是一个好习惯吗?

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

  

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

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

  

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

     

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

由于上述问题。

  

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

没有上下文就很难回答。

答案 2 :(得分:3)

你可以这样做,但是例外

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

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

  3. 所以你的异常构造函数必须

    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


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

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

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

答案 3 :(得分:1)

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

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

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