21

是否安全:

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. 在需要时使用它(投掷)

new而不是每次都抛出异常?

我有兴趣是否安全

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

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

4

6 回答 6

20

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

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
    在 ThrowTest.<clinit>(ThrowTest.java:2)
t2:
java.lang.Exception: t2
    在 ThrowTest.t2(ThrowTest.java:31)
    在 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
    在 ThrowTest.buildEx(ThrowTest.java:24)
    在 ThrowTest.t1(ThrowTest.java:29)
    在 ThrowTest.main(ThrowTest.java:8)
t2:
java.lang.Exception: t2
    在 ThrowTest.t2(ThrowTest.java:34)
    在 ThrowTest.main(ThrowTest.java:15)
于 2013-02-26T13:53:39.990 回答
7

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

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

令人惊讶的是,Throwable它实际上是线程安全的,因为天知道。所以如果一个异常被多个线程修改,至少不会出现灾难性的故障。但是会出现逻辑故障。

如果应用程序不断向这个长期存在的异常添加抑制的异常,内存泄漏也是可能的。

于 2013-02-26T16:28:10.430 回答
3

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

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

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

于 2013-02-26T13:52:57.840 回答
2

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

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

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

于 2013-02-26T13:53:25.093 回答
2

使用任何错误处理代码都是不安全的。保持简单,保持明显,不要写任何你需要提问的东西。错误处理不会引发额外的错误。

于 2013-02-26T22:59:25.103 回答
1

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

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

此外,您的代码可能会变得非常具有误导性,这为一些非常困难的事情增加了很多开销。

于 2013-02-26T13:52:03.413 回答