在Java中,抛出检查异常(异常或其子类型-IOException,InterruptedException等(的方法必须声明throws语句:
public abstract int read() throws IOException;
不声明语句的方法throws
不能引发检查异常。
public int read() { // does not compile
throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown
但是在安全方法中捕获检查的异常在java中仍然是合法的:
public void safeMethod() { System.out.println("I'm safe"); }
public void test() { // method guarantees not to throw checked exceptions
try {
safeMethod();
} catch (Exception e) { // catching checked exception java.lang.Exception
throw e; // so I can throw... a checked Exception?
}
}
其实不然。这有点有趣:编译器知道 e 不是一个经过检查的异常,并允许重新抛出它。事情甚至有点荒谬,这段代码不编译:
public void test() { // guarantees not to throw checked exceptions
try {
safeMethod();
} catch (Exception e) {
throw (Exception) e; // seriously?
}
}
// Error: unreported exception java.lang.Exception; must be caught or declared to be thrown
第一个片段是一个问题的动机。
编译器知道检查的异常不能在安全的方法中抛出 - 所以也许它应该只允许捕获未经检查的异常?
回到主要问题 - 是否有任何理由以这种方式实现捕获检查的异常?这只是设计中的缺陷还是我错过了一些重要因素 - 也许是向后不兼容? 如果在这种情况下只允许捕获RuntimeException
,可能会出现什么问题?非常感谢示例。
引用Java语言规范§11.2.3:
如果 catch 子句可以捕获选中的异常类 E1,则编译时错误,并且对应于catch 子句的 try 块不能抛出作为 E1 的子类或超类的已检查异常类的情况,除非 E1 是异常或异常的超类。
我猜这条规则早在Java 7之前就已经出现,当时不存在多重捕获。因此,如果你有一个可以抛出大量异常的try
块,那么捕获所有内容的最简单方法是捕获一个公共超类(在最坏的情况下,Exception
,或者Throwable
如果你想捕获Error
s(。
请注意,您可能不会捕获与实际抛出的内容完全无关的异常类型 - 在您的示例中,捕获不是RuntimeException
的任何Throwable
子类都将是一个错误:
try {
System.out.println("hello");
} catch (IOException e) { // compilation error
e.printStackTrace();
}
由OP编辑:答案的主要部分是问题示例仅适用于异常类。通常,不允许在代码的随机位置捕获已检查的异常。抱歉,如果我使用这些示例使某人感到困惑。
Java 7引入了更具包容性的异常类型检查。
但是,在 Java SE 7 中,您可以在 rethrowException 方法声明的 throws 子句中指定异常类型 FirstException 和 SecondException。Java SE 7 编译器可以确定语句 throw e 抛出的异常一定来自 try 块,而 try 块抛出的唯一异常可以是 FirstException 和 SecondException。
这段话讲的是一个专门抛出FirstException
和SecondException
的try
块;即使catch
块抛出Exception
,该方法只需要声明它抛出FirstException
和SecondException
,而不是Exception
:
public void rethrowException(String exceptionName) throws FirstException, SecondException { try { // ... } catch (Exception e) { throw e; } }
这意味着编译器可以检测到test
中抛出的唯一可能的异常类型是 Error
s 或 RuntimeException
s,两者都不需要捕获。 当你throw e;
时,即使静态类型是Exception
,它也可以告诉它不需要被声明或重新捕获。
但是当你把它投射到Exception
,这绕过了这个逻辑。 现在编译器将其视为需要捕获或声明的普通Exception
。
将此逻辑添加到编译器的主要原因是允许程序员在重新抛出通用Exception
捕获这些特定子类型时,仅在 throws
子句中指定特定的子类型。 但是,在这种情况下,它允许您捕获常规Exception
,而不必在 throws
子句中声明任何异常,因为可以引发的任何特定类型都不是检查异常。
这里的问题是,选中/未选中的异常限制会影响允许代码抛出的内容,而不是允许捕获的内容。 虽然您仍然可以捕捉任何类型的Exception
,但您唯一可以再次实际投掷的都是未经检查的。 (这就是为什么将未选中的异常转换为已检查的异常会破坏代码的原因。
使用 Exception
捕获未选中的异常是有效的,因为未选中的异常(也称为 RuntimeException
s( 是异常的子类,它遵循标准的多态性规则;它不会将捕获的异常变成Exception
,就像将String
存储在Object
中不会将String
变成Object
一样。 多态性意味着可以保存Object
的变量可以保存从Object
派生的任何内容(例如String
(。 类似地,由于Exception
是所有异常类型的超类,因此类型为Exception
的变量可以保存从Exception
派生的任何类,而无需将对象转换为Exception
。 考虑一下:
import java.lang.*;
// ...
public String iReturnAString() { return "Consider this!"; }
// ...
Object o = iReturnAString();
尽管变量的类型是Object
的,o
仍然存储一个String
,不是吗? 同样,在您的代码中:
try {
safeMethod();
} catch (Exception e) { // catching checked exception
throw e; // so I can throw... a checked Exception?
}
这实际上意味着"捕获与类Exception
兼容的任何内容(即 Exception
以及从中衍生出的任何东西(。 类似的逻辑也用于其他语言;例如,在C++中,捕获std::exception
还将捕获std::runtime_error
、std::logic_error
、std::bad_alloc
、任何正确定义的用户创建的异常等等,因为它们都派生自std::exception
。
dr:你没有捕获已检查的异常,而是捕获了任何异常。 仅当将异常转换为已检查异常类型时,该异常才会成为已检查的异常。