了解异常处理 - 重新抛出异常

Understanding exception handling - Rethrowing exceptions

考虑以下代码片段

在代码片段1中,方法m1()在throws声明中有SQLException,但实际上抛出的是一个Exception类型的引用变量。我在这里期待编译器错误,因为 Exception 没有在 throws 声明中提到。但它编译并打印 Caught successfully

import java.sql.SQLException;
public class Snippet1{
    private static void m() throws SQLException{
        try{
            throw new SQLException();
        } catch(Exception e){
            throw e;
        }
    }

    public static void main(String[] args){
        try{
            m();
        } catch(SQLException e){
            System.out.println("Caught successfully"); //prints "Caught successfully
        }
    }
}

代码片段2与上一个几乎相同,只是我们将null赋值给异常引用变量e,然后将其抛出。现在编译器抱怨 Exception must be catched or declared to be thrown.

import java.sql.SQLException;
public class Snippet2{
    private static void m() throws SQLException{
        try{
            throw new SQLException();
        } catch(Exception e){
            e = null;
            throw e;
        }
    }

    public static void main(String[] args){
        try{
            m();
        } catch(SQLException e){
            System.out.println("Caught successfully");
        }
    }
}

我不明白为什么 #1 可以编译而 #2 不能编译。

这在 JDK 7 Rethrowing Exceptions with More Inclusive Type Checking

中有描述
public void rethrowException(String exceptionName)
 throws FirstException, SecondException {
   try {
     // ...
   }
   catch (Exception e) {
     throw e;
   }
 }

The Java SE 7 compiler can determine that the exception thrown by the statement throw e must have come from the try block, and the only exceptions thrown by the try block can be FirstException and SecondException. Even though the exception parameter of the catch clause, e, is type Exception, the compiler can determine that it is an instance of either FirstException or SecondException

This analysis is disabled if the catch parameter is assigned to another value in the catch block. However, if the catch parameter is assigned to another value, you must specify the exception type Exception in the throws clause of the method declaration.

JLS 中有一个相当特殊的巫毒魔法规则,在 JDK6 之后的某个时候引入(我认为在 JDK7 中,与 'multi-catch' 一起,您可以在其中命名多个异常类型,并用竖线分隔(|) 字符).

如果您捕捉到 'overly broad' 异常类型,并且变量是 final 或 'effectively final'(绝不是 re-assigned),那么任何 throw t; 语句其中t 是变量被视为仅表示关联的 try 主体实际可能抛出的异常类型。

换句话说:

  • 在第一个片段中,您的 Exception e 实际上是最终的,因此规则开始生效。
  • 鉴于规则开始生效,片段 1 中的 throw e; 被视为 e 实际上被收紧为 try 主体可以抛出的所有事物的不相交类型,这些事物被键入为Exception 或其某些子类型。在这种情况下,这意味着:只是 SQLException。方法体被声明为 throws SQLException,因此,throw e; 是可以接受的。
  • 在第二个片段中,e 不再有效,因为它是 re-assigned。因此这条规则不会生效,并且 throw e; 被简单地解释为试图抛出 Exception,这是不合法的,因为方法体没有 throws 它和 throw 语句不在 try 块中,其 catch 块处理 Exception.

添加此功能是为了更简单地编写想要在抛出异常时 'peek' 的代码 - 他们想在发生异常时执行某些操作,但随后他们希望异常处理继续进行,就好像他们没有,即异常仍然被抛出,完全没有改变。这或多或少是 finally 的用途,除了 finallyall 情况下运行:所有异常类型,即使 try 主体正常完成或控制也运行流出。

官方文档的相关部分是Java Language Specification §11.2.2