我的安全重新安全有多安全?

时间:2009-10-30 16:18:48

标签: java exception

延迟编辑:这个问题有望在Java 7出现时过时,因为"final rethrow" feature seems like it will be added


很多时候,我发现自己处于这样的情况:

    do some initialization
    try {
        do some work 
    } catch any exception {
        undo initialization
        rethrow exception
    }

在C#中你可以这样做:

InitializeStuff();
try
{
    DoSomeWork();
}
catch 
{
    UndoInitialize();
    throw;
}

对于Java来说,没有好的替代品,而且从the proposal for improved exception handling was cut from Java 7开始,看起来好几年才能得到类似的东西。因此,我决定自己动手:

编辑:半年后,final rethrow is back,或者看起来如此。)

public final class Rethrow {

    private Rethrow() { throw new AssertionError("uninstantiable"); }

    /** Rethrows t if it is an unchecked exception. */
    public static void unchecked(Throwable t) {
        if (t instanceof Error)
            throw (Error) t;
        if (t instanceof RuntimeException)
            throw (RuntimeException) t;
    }

    /** Rethrows t if it is an unchecked exception or an instance of E. */
    public static <E extends Exception> void instanceOrUnchecked(
            Class<E> exceptionClass, Throwable t) throws E, Error,
            RuntimeException {
        Rethrow.unchecked(t);
        if (exceptionClass.isInstance(t))
            throw exceptionClass.cast(t);
    }

}

典型用法:

public void doStuff() throws SomeException {
    initializeStuff();
    try {
        doSomeWork();
    } catch (Throwable t) {
        undoInitialize();
        Rethrow.instanceOrUnchecked(SomeException.class, t);
        // We shouldn't get past the above line as only unchecked or 
        // SomeException exceptions are thrown in the try block, but
        // we don't want to risk swallowing an error, so:
        throw new SomeException("Unexpected exception", t); 
    }
    private void doSomeWork() throws SomeException { ... }
}

这有点罗嗦,抓住Throwable通常不赞成,我真的很高兴使用反思只是为了重新抛出异常,我总是觉得有点不安写“这不会发生”的评论,但在实践中它运作良好(或似乎至少)。我想知道的是:

  1. 我的重新抛出辅助方法有任何缺陷吗?我错过了一些角落案件? (我知道Throwable可能是由于某种严重的问题导致我的undoInitialize失败,但那没关系。)
    • 有人已经发明了这个吗?我看了Commons Lang的ExceptionUtils,但还有其他的事情。

  2. 修改

    • finally不是我正在寻找的机器人。我只对在抛出异常时做的事情感兴趣。
    • 是的,我知道捕捉Throwable是一个很大的禁忌,但我认为这里有较小的邪恶,而不是有三个捕获条款(ErrorRuntimeException和{{ 1}},分别使用相同的代码。
    • 请注意,我并没有试图压制任何错误 - 我的想法是,只要我重新启动了一些内容,SomeException块中抛出的任何异常都将继续通过调用堆栈冒出来。

3 个答案:

答案 0 :(得分:5)

有两种方法可以解决这个问题。如果您不需要知道异常是什么,首先是我的偏好。

boolean okay = false;
try {
  // do some work which might throw an exception
  okay = true;
} finally {
  if (!okay) // do some clean up.
}

在某些情况下,您可以在没有额外变量的情况下执行相同的操作,具体取决于try块的功能。

第二种选择是黑客,但也有效。

try {
    // do some work which might throw an exception
} catch (Throwable t) {
    // do something with t.
    Thread.currentThread().stop(t);
}

stop(Throwable t)方法不会停止线程,而是导致线程以未经检查的方式抛出异常。

你可以使用Unsafe.throwException()进行一些摆弄,并且有一种方法可以使用我遗忘的泛型来做到这一点。

答案 1 :(得分:1)

如果您担心会发生未初始化,那么您可能只想将该代码放入finally块中,因为如果应该在某个时刻调用它,那么您可能应该总是清理它。

我很想抓住Throwable作为我想要处理的一些例外,有些我只是记录,因为没有使用传递用户无法做任何事情的例外,例如NullPointerException

但是,你没有显示SomeException被定义为什么,但如果抛出OutOfMemoryException,你的throwable会捕获它,但它可能与{{1}不同因此,至少在我查看SomeException方法时,您的示例函数中将需要您的包装器。

您可能希望编写单元测试,尝试不同类别的异常,并查看哪些功能有效或无效,因此您可以记录预期的行为。

答案 2 :(得分:1)

另一种方法是让工厂只在原因是经过检查的异常时创建SomeException:

   public static SomeException throwException(String message, Throwable cause) throws SomeException {
      unchecked(cause); //calls the method you defined in the question.
      throw new SomeException(message, cause);
   }

我在方法中输入返回值的原因是客户端可以这样做:

     catch (Throwable e) {
         undoInitialize();
         throw SomeException.throwException("message", e);
     }

如果方法具有返回类型,编译器被假装成不需要在catch语句之后返回,但如果客户端忘记在调用factory方法之前放置throw,它仍会抛出异常。

这对你的代码的缺点是它不太可移植(它适用于SomeException,但不适用于SomeOtherException),但这可能没问题,因为它不适用于你需要拥有的每个异常类型撤消初始化。

如果它适合您的用例,您可以将未经检查的调用放在SomeException的构造函数中,并将逻辑提供给所有子类,但这必须适合您的特定项目 - 在一般情况下这不是一个好主意因为它会阻止包装运行时异常。

      public SomeException(message, cause) {
            super(message, unchecked(cause));
      }

      private static Throwable unchecked(Throwable cause) {
          if (cause instanceof Error) throw (Error) cause;
          if (cause instanceof RuntimeException) throw (RuntimeException) cause;
          return cause;
      }