1
try {
        throw new FileNotFoundException();
    } catch (IOException e) {
        e.printStackTrace();
    }
    catch (Exception e) {
        e.printStackTrace();
    }

有人能告诉我为什么第二个 catch 块不被编译器视为无法访问的代码吗?但在以下情况下:

try {
        throw new FileNotFoundException();
    } catch (Exception e) {
        e.printStackTrace();
    }
    catch (IOException e) {
        e.printStackTrace();
    }

第二个 catch 块被认为是不可达的?

毕竟,FileNotFoundException 属于 IOException,就像它属于 Exception 一样。

编辑请澄清:编译器将根据方法的 throws 子句知道方法引发了异常。但它可能不一定知道异常的具体类型(在该异常类别下)。因此,如果方法抛出异常“A”,编译器将不知道实际异常是“A”还是“A”的子类型,因为这仅在运行时确定。然而,编译器会知道不会抛出“X”类型的异常,因此为 X 提供 catch 块是错误的。这是正确的吗?

4

3 回答 3

4

编译器不能假定从您的try块中抛出的唯一可能异常是FileNotFoundException. 这就是为什么它不认为catch在您的第一个代码示例中无法访问第二个块。

如果由于某种未知原因,在创建实例RuntimeException时抛出了 a (完全有可能)怎么办?FileNotFoundException然后怎样呢?

在您的第一个代码示例中,意外的运行时异常将被第二个catch块捕获,而第一个块将处理FileNotFoundException它是否被抛出。

但是,在您的第二个代码示例中,任何和所有异常都会被第一个catch块捕获,从而使第二个块无法访问。

编辑:

为了更好地理解为什么catch(Exception e)您的第一个代码中的块不被编译器认为是不可访问的,请尝试以下代码,并注意第二个catch是如何绝对可访问的:

public class CustomIOException extends IOException {
    public CustomIOException(boolean fail) {
        if (fail) {
            throw new RuntimeException("the compiler will never know about me");
        }
    }
}

public static void main(String[] args) {
    try {
        throw new CustomIOException(true);
    } catch(IOException e) {
        System.out.println("Caught some IO exception: " + e.getMessage());
    } catch(Exception e) {
        System.out.println("Caught other exception: " + e.getMessage());
    }
}

输出:

发现其他异常:编译器永远不会知道我

于 2015-12-10T00:43:54.877 回答
2

TL;博士

编译器认为这FileNotFoundException()可能不是唯一Exception抛出的。


解释

JLS§11.2.3 异常检查

如果 catch 子句可以捕获(第 11.2 节)已检查的异常类 E1,并且对应于该 catch 子句的 try 块可以抛出已检查的异常类 E2、E1 的子类和前面的 catch 子句,则鼓励 Java 编译器发出警告立即封闭的 try 语句可以捕获已检查的异常类 E3,其中 E2 <: E3 <: E1。

这意味着如果编译器认为你的 catch 块可能抛出的唯一异常是 a FileNotFoundException(),它会警告你关于你的第二个 catch 块。这不是这里的情况。

但是,下面的代码

    try{
        throw new FileNotFoundException();
    } catch (FileNotFoundException e){
        e.printStackTrace();
    } catch (IOException e){ // The compiler warns that all the Exceptions possibly 
                             // catched by IOException are already catched even though
                             // an IOException is not necessarily a FNFException
        e.printStackTrace();
    } catch (Exception e){
        e.printStackTrace();
    }

发生这种情况是因为编译器评估 try 块以确定哪些异常有可能被抛出。

由于编译器不会警告我们Èxception e,它认为可能会抛出其他异常(例如 RunTimeException)。由于处理这些 RunTimeExceptions 不是编译器的工作,它让它溜走。


其余的答案很有趣,可以理解异常捕获背后的机制。


架构

如您所见,Exception它在层次结构中处于较高位置,因此必须在IOException层次结构中处于较低位置之后才声明它。

在此处输入图像描述


例子

想象一下有一个IOException抛出。由于它继承自Exception,我们可以说 IOException IS-A Exception ,因此它总是会在Exception块中被捕获,并且IOException块将无法访问。


现实生活中的例子

比方说,你在一家商店,必须选择裤子。卖家告诉你,裤子从大到小都要试一试,如果找到可以穿的(即使不是你的尺码)你一定要拿下。

你会发现自己买的裤子对你的尺码来说太大了,而且你没有机会找到适合你的裤子。

你去另一家商店:在那里,你发生了完全相反的事情。你可以从最小到最大选择你的裤子,如果你找到一条你可以穿的,你必须把它拿走。

您会发现自己购买的裤子与您的尺码完全一致。

这有点类比,有点奇怪,但它不言自明。


从 Java 7 开始:multi-catch

从 Java 7 开始,您可以选择将 try 块可能抛出的所有异常类型包含在一个且仅 catch 块中。

警告:您还必须尊重层次结构,但这次是从左到右。

在你的情况下,这将是

try{
    //doStuff
}catch(IOException | Exception e){
    e.printStackTrace();
}

以下示例在 Java SE 7 及更高版本中有效,消除了重复代码:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

catch 子句指定块可以处理的异常类型,每种异常类型用竖线 (|) 分隔。

于 2015-12-10T00:51:33.537 回答
0

第一种情况:

catch (IOException e) { // A specific Exception
    e.printStackTrace();
}
catch (Exception e) { // If there's any other exception, move here
    e.printStackTrace();
}

如您所见,首先IOException被捕获。这意味着我们只针对一个特定的例外。然后在第二个捕获中,我们的目标是除IOException. 因此它是合乎逻辑的。

在第二个:

catch (Exception e) { // Move here no matter whatever exception
    e.printStackTrace();
}
catch (IOException e) { // The block above already handles *Every exception, hence this won't be reached.
    e.printStackTrace();
}

我们在第一个块中捕获了任何异常(无论是它IOException还是其他异常)。因此将无法到达第二个块,因为所有内容都已包含在第一个块中。

换句话说,在第一种情况下,我们针对的是某个特定的例外,而不是任何其他例外。而在第二种情况下,我们首先针对所有/任何异常,而不是针对特定异常。而且由于我们已经处理了所有异常,因此稍后再有一个特定的异常将没有任何逻辑意义。

于 2015-12-10T00:41:44.723 回答