存储Exception实例并重用它是否安全?

时间:2013-02-26 13:43:14

标签: java exception exception-handling

安全:

public class Widget {

    private static final IllegalStateException LE_EXCEPTION
            = new IllegalStateException("le sophisticated way");

    ...

   public void fun() {
      // some logic here, that may throw
      throw LE_EXCEPTION;
   }

   ....
}
  1. 保留例外情况
  2. 在需要时使用它(扔)
  3. 而不是每次都抛出new例外?

    我感兴趣,如果它是安全的

    安全我的意思是:没有内存损坏,没有JVM抛出的额外异常,缺少类,没有加载坏类(...)。注意:异常将通过网络抛出(远程处理)。

    其他问题(可读性,保留实例的成本)并不重要。

6 个答案:

答案 0 :(得分:19)

这取决于您对“安全”的定义。该异常将给出误导性的堆栈跟踪,我不称之为“安全”。考虑:

public class ThrowTest {
    private static Exception e = new Exception("t1"); // Line 2

    public static final void main(String[] args) {
        ThrowTest tt;

        tt = new ThrowTest();
        try {
            tt.t1();
        }
        catch (Exception ex) {
            System.out.println("t1:");
            ex.printStackTrace(System.out);
        }
        try {
            tt.t2();                                  // Line 16
        }
        catch (Exception ex) {
            System.out.println("t2:");
            ex.printStackTrace(System.out);
        }
    }

    private void t1() 
    throws Exception {
        throw this.e;
    }

    private void t2() 
    throws Exception {
        throw new Exception("t2");                    // Line 31
    }
}

有这个输出:

$ java ThrowTest
t1:
java.lang.Exception: t1
    at ThrowTest.<clinit>(ThrowTest.java:2)
t2:
java.lang.Exception: t2
    at ThrowTest.t2(ThrowTest.java:31)
    at ThrowTest.main(ThrowTest.java:16)

注意第一个测试用例中堆栈跟踪完全缺少t1方法。根本没有有用的背景信息。

现在,您可以使用fillInStackTrace在投掷前填写该信息:

this.e.fillInStackTrace();
throw this.e;

...但这只是为自己工作(你的工作有时会忘记)。根本没有任何好处。并非所有异常都允许您这样做(一些例外使堆栈跟踪成为只读)。


你在评论的其他地方已经说过,这是为了避免“代码重复”。拥有异常构建器功能,你 更好

private IllegalStateException buildISE() {
    return new IllegalStateException("le sophisticated way");
}

(如果您愿意,可以static final。)

然后像这样抛出它:

throw buildISE();

这可以避免代码重复,而不会误导堆栈跟踪和不必要的异常实例。

以下是适用于上述内容的内容:

public class ThrowTest {

    public static final void main(String[] args) {
        ThrowTest tt;

        tt = new ThrowTest();
        try {
            tt.t1();                                   // Line 8
        }
        catch (Exception ex) {
            System.out.println("t1:");
            ex.printStackTrace(System.out);
        }
        try {
            tt.t2();                                   // Line 15
        }
        catch (Exception ex) {
            System.out.println("t2:");
            ex.printStackTrace(System.out);
        }
    }

    private static final Exception buildEx() {
        return new Exception("t1");                    // Line 24
    }

    private void t1() 
    throws Exception {
        throw buildEx();                               // Line 29
    }

    private void t2() 
    throws Exception {
        throw new Exception("t2");                     // Line 34
    }
}
$ java ThrowTest
t1:
java.lang.Exception: t1
    at ThrowTest.buildEx(ThrowTest.java:24)
    at ThrowTest.t1(ThrowTest.java:29)
    at ThrowTest.main(ThrowTest.java:8)
t2:
java.lang.Exception: t2
    at ThrowTest.t2(ThrowTest.java:34)
    at ThrowTest.main(ThrowTest.java:15)

答案 1 :(得分:6)

这是不安全的,除非异常是不可变的(即enableSuppression = writableStackTrace = false)。

如果异常不是不可变的,则可以通过捕获器修改它 - 设置新的堆栈跟踪或添加抑制的异常。如果有多个捕手试图修改异常,那就会出现混乱。

令人惊讶的是,Throwable实际上是线程安全的,因为上帝知道什么。因此,如果多个线程修改了异常,那么至少不会发生灾难性故障。但是会出现逻辑上的失败。

如果应用程序不断向此长期异常添加抑制异常,则也可能发生内存泄漏。

答案 2 :(得分:3)

不这样做的另一个原因是堆栈跟踪不合适。

无论你在代码中的什么位置抛出异常,当打印它的堆栈跟踪时,它将显示异常初始化的行而不是抛出它的行(在这种特殊情况下代替预期Widget.fun()堆栈跟踪将包含Widget.<clinit>)。

因此,您(或使用您的代码的人)将无法确定错误的实际位置。

答案 3 :(得分:2)

它认为在读取异常堆栈跟踪时会导致问题。代码抛出异常时会填充堆栈跟踪,但会将其存储到异常本身的实例中。如果你抛出相同的异常实例两次,我认为getStackTrace()返回最后一个堆栈跟踪。如果由于某种原因(例如在多线程环境中)异常从代码中的不同位置抛出两次然后打印,则从第一次抛出的堆栈跟踪可能是错误的。

由于没有理由重复使用异常的实例,我不建议您这样做。

如果要将异常用作一种容纳其他信息的容器(例如错误消息,错误代码等),请使用异常可序列化的事实:在抛出之前克隆它。因此,每次您将抛出异常的唯一实例,但其字段将从预先创建的模板中复制。

答案 4 :(得分:2)

使用任何错误处理代码都不安全。保持简单,保持明显,不要写任何你需要提出的问题。错误处理不会产生额外的错误。

答案 5 :(得分:1)

我认为这是错误的,因为它会鼓励您使用使用例外来描述正常情况,而不仅仅是例外情况。参见J. Bloch的Effective Java second edition,第241页,第57项。

此外,您始终在堆中保留一个对象,因此这不是必需的,因为在现代JVM中对象创建速度非常快,并且一旦抛出异常,它可能很快就会被垃圾收集。

此外,您的代码可能会变得非常误导,这为相当紧张的事情增加了大量开销。