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

3
考虑下面的代码片段。
在代码片段1中,方法m1()在throws声明中有SQLException,但实际上它抛出了一个类型为Exception的引用变量。我原本以为会出现编译器错误,因为throws声明中没有提到Exception。但是它可以编译并打印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 必须被捕获或声明抛出。

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 不能。

2个回答

1

这在JDK 7中有描述:使用更全面的类型检查重新抛出异常

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.


2
请注意,引用的描述略有不准确。编译器可以确定异常是FirstExceptionSecondException...或未经检查的异常。重点是catch(Exception e){throw e;}可以捕获并重新抛出(例如)NullPointerException。就JLS而言,这是可以接受的。 - Stephen C

0

JLS中有一条非常特殊的巫术魔法规则,介绍于JDK6之后的某个时候(我认为是在JDK7中与'multi-catch'一起引入的,在其中您可以使用竖杆(|)字符命名多个异常类型)。

如果您捕获的是一个“过于宽泛”的异常类型,并且该变量是final或“有效地是final”(从未重新分配过),那么任何throw t;语句,其中t就是该变量,则被视为只代表与相关try体实际可能抛出的异常类型相对应的异常类型。

换句话说:

在第一个片段中,你的Exception e有效地是final的,因此规则生效了。
鉴于规则生效,片段1中的throw e;被视为实际上将e收紧为所有try体可以抛出的类型为Exception或某个其子类型的不相交类型。在这种情况下,这意味着:只有SQLException。方法体声明为throws SQLException,因此,throw e;是可接受的。
在第二个片段中,e不再是effective final,因为它被重新赋值。因此,这个规则不会生效,throw e;被简单地解释为试图抛出Exception,而这不合法,因为方法体没有throws它,而且throw语句不在一个try块中,catch块处理Exception

这个功能的添加是为了让编写代码更简单,以便“窥视”抛出的异常 - 当异常发生时,他们想要做一些事情,但是他们希望异常处理继续进行,就好像他们没有这样做一样,即异常仍然被抛出,完全不变。这基本上就是 finally 的作用,除了 finally 在所有情况下都运行:所有异常类型,即使 try 体正常完成或控制流程流出也会运行。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接