【问题标题】:Exception hierarchy/try-multi-catch异常层次结构/try-multi-catch
【发布时间】:2015-12-10 00:38:10
【问题描述】:
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 块是错误的。是这样吗?

【问题讨论】:

  • 您可能想澄清您的问题是关于第一种还是第二种情况。我认为您的问题是关于第一种情况下的catch (Exception e),以及为什么编译器不认为它无法访问。但看起来这里的大多数人都认为你在问你的第二个例子。是哪个?
  • 我想回答的人都足够清楚了。问题是关于这两种情况,即为什么第一种情况没有错误,而第二种情况有错误。

标签: java exception exception-handling checked-exceptions


【解决方案1】:

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

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

在您的第一个代码示例中,意外的运行时异常将被第二个 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());
    }
}

输出:

捕获其他异常:编译器永远不会知道我

【讨论】:

    【解决方案2】:

    TL;DR

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


    说明

    JLS§11.2.3 Exception Checking

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

    这意味着如果编译器认为你的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 之后最后声明。


    示例

    想象一下有一个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 子句指定块可以处理的异常类型 句柄,每个异常类型用竖线 (|) 分隔。

    【讨论】:

    • 这并不能解释为什么catch (Exception e) 在问题的第一个示例中不是无法访问。
    • @ErwinBolwidt 不抱歉,我确实很好地理解了这个问题。 OP刚刚编辑了它。
    【解决方案3】:

    第一种情况:

    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 还是其他一些异常)。因此,将无法到达第二个块,因为所有内容都已包含在第一个块中。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-16
      • 1970-01-01
      • 1970-01-01
      • 2011-04-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-09
      • 2015-06-21
      相关资源
      最近更新 更多