异常层次结构/ try-multi-catch

时间:2015-12-10 00:38:10

标签: java exception exception-handling checked-exceptions

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();
    }

第二个阻止块被认为无法访问?

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

修改请澄清: 编译器将知道基于方法的throws子句的方法抛出异常。但它可能不一定知道特定类型的异常(在该类异常下)。因此,如果方法抛出异常'A',编译器将不知道实际异常是'A'还是'A'的子类型,因为这仅在运行时确定。然而,编译器将知道永远不会抛出类型为“X”的异常,因此为X提供catch块是错误的。这是对的吗?

3 个答案:

答案 0 :(得分:4)

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

如果出于某种未知原因,在创建RuntimeException实例时完全可能会抛出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());
    }
}

输出:

  

抓到其他异常:编译器永远不会了解我

答案 1 :(得分:2)

TL; DR

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

JLS§11.2.3 Exception Checking

  

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

这意味着如果编译器认为catch块可能引发的唯一异常是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之后最后声明。

enter image description here

实施例

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

真实生活示例

让我们说,你在商店,必须选择裤子。卖家告诉你,你必须尝试从最大的裤子到最小的裤子,如果你找到一件你可以穿的(即使它不是你的尺码)你必须服用。

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

你去另一家商店:那里,你正好相反。你可以选择从最小到最大的裤子,如果你找到一件可以穿的,你必须服用。

你会发现自己买的裤子尺寸正确。

这有点类比,有点奇怪,但它说明了一切。

从Java 7开始:multi-catch

从Java 7开始,您可以选择在try和block中包含可能由try块抛出的所有类型的异常。

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

在你的情况下,它将是

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

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

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

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

答案 2 :(得分: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还是其他异常)。因此,将无法访问第二个块,因为所有内容都已包含在第一个块中。

换句话说,在第一种情况下,我们的目标是某些特定的例外,而不是任何其他例外。在第二种情况下,我们首先针对所有/任何异常,而不是针对特定异常。而且由于我们已经处理了所有例外情况,因此稍后具有特定异常将无法理解。