新的Java程序员经常遇到这样的错误:
"error: unreported exception <XXX>; must be caught or declared to be thrown"
其中XXX是某个异常类的名称。
请解释:
- 编译错误消息是怎么说的
- 此错误背后的Java概念,以及
- 如何修复
第一件事。这是一个编译错误,不是一个例外。您应该在编译时看到它。
如果您在运行时异常消息中看到它,那可能是因为您运行的某些代码中存在编译错误。请返回并修复编译错误。然后在IDE中找到并设置阻止它生成"的设置;。类";存在编译错误的源代码的文件。(为自己省去未来的痛苦。)
这个问题的简短答案是:
-
错误消息表示,包含此错误的语句正在抛出(或传播)检查的异常,并且该异常(
XXX
)没有得到正确处理。 -
解决方案是通过以下任一方式处理异常:
- 用
try ... catch
语句捕获并处理它,或者 - 声明封闭方法或构造函数CCD_ 3 it1
- 用
1-有些边缘情况下你无法做到这一点。阅读剩下的答案
已检查与未检查异常
在Java中,异常由java.lang.Throwable
类的派生类表示。例外情况分为两类:
-
除了
RuntimeException
及其子类之外,检查的异常是Throwable
和Exception
及其子类 -
未选中的异常是所有其他异常;即
Error
及其子类和RuntimeException
及其子类
(在上文中,"子类"包括直接和间接子类。)
检查异常和未检查异常之间的区别在于,检查异常必须是";处理";在封闭方法或构造函数中发生异常,但不需要处理未检查的异常。
(问:你怎么知道是否检查了异常?答:找到异常类的javadoc,并查看其父类。)
如何处理(已检查的)异常
从Java语言的角度来看,有两种方法可以处理异常;满足";编译器:
-
您可以在
try ... catch
语句中捕获异常。例如:public void doThings() { try { // do some things if (someFlag) { throw new IOException("cannot read something"); } // do more things } catch (IOException ex) { // deal with it <<<=== HERE } }
在上文中,我们将抛出(已检查的)
IOException
的语句放在try
的主体中。然后我们编写了一个catch
子句来捕获异常。(我们可以捕获IOException
的超类……但在这种情况下,它将是Exception
,捕获Exception
是个坏主意。) -
您可以声明封闭方法或构造函数
throws
为异常public void doThings() throws IOException { // do some things if (someFlag) { throw new IOException("cannot read something"); } // do more things }
在上面我们已经声明了
doThings()
抛出IOException
。这意味着任何调用doThings()
方法的代码都必须处理该异常。简而言之,我们将处理异常的问题传递给调用者。
以下哪项是正确的做法?
这取决于上下文。然而,一般原则是,您应该在代码中能够适当处理异常的级别处理异常。这反过来又取决于异常处理代码要做什么(在HERE
)。它能恢复吗?它能放弃当前的请求吗?它应该停止应用程序吗?
解决问题
概括一下。编译错误意味着:
- 您的代码抛出了一个检查异常,或者调用了一些抛出检查异常的方法或构造函数,并且
- 它没有通过捕获异常或按照Java语言的要求声明异常来处理
您的解决方案流程应该是:
- 了解异常的含义,以及为什么会被抛出。读取异常的javadoc和引发异常的方法
- 根据1,阅读您的代码,并决定处理可能的异常的正确方法
- 基于2,对代码进行相关更改
示例:用相同的方法投掷和接球
考虑下面这个问答的例子;
public class Main {
static void t() throws IllegalAccessException {
try {
throw new IllegalAccessException("demo");
} catch (IllegalAccessException e){
System.out.println(e);
}
}
public static void main(String[] args){
t();
System.out.println("hello");
}
}
如果你一直在关注我们到目前为止所说的,你会意识到t()
将给出";未报告的异常";编译错误。在这种情况下,错误在于t
已被声明为throws IllegalAccessException
。事实上,异常不会传播,因为它已经在引发它的方法中被捕获
本例中的修复方法是删除throws IllegalAccessException
。
这里的小教训是throws IllegalAccessException
是一个方法,它表示调用者应该期望传播异常。这实际上并不意味着它会传播。另一方面,如果不希望传播异常(例如,因为它没有被抛出,或者因为它被捕获而没有重新抛出),那么方法的签名就不应该说它被抛出了!
有例外的不良做法
有几件事你应该避免做:
不要将
Exception
(或Throwable
)作为捕获异常列表的捷径。如果你这样做,你很可能会发现你意想不到的事情(比如未检查NullPointerException
),然后在不该恢复的时候尝试恢复。不要将方法声明为
throws
0。这迫使被调用处理(可能)任何已检查的异常。。。这简直是一场噩梦。不要压制例外情况。例如
try { ... } catch (NullPointerException ex) { // It never happens ... ignoring this }
如果压缩异常,则可能会使触发异常的运行时错误更难诊断。你在破坏证据。
注意:仅仅相信从未发生过异常(根据注释)并不一定会使其成为事实。
边缘情况:静态初始化程序
在某些情况下,处理已检查的异常是个问题。一种特殊情况是static
初始化程序中的检查异常。例如:
private static final FileInputStream input = new FileInputStream("foo.txt");
FileInputStream
被声明为throws FileNotFoundException
。。。这是一个已检查的异常。但由于上面是一个字段声明,Java语言的语法不允许我们将声明放在try
中。。。CCD_ 35。并且没有合适的(封闭的)方法或构造函数。。。因为这段代码是在初始化类时运行的。
一种解决方案是使用static
块;例如:
private static final FileInputStream input;
static {
FileInputStream temp = null;
try {
temp = new FileInputStream("foo.txt");
} catch (FileNotFoundException ex) {
// log the error rather than squashing it
}
input = temp; // Note that we need a single point of assignment to 'input'
}
(在实际代码中有更好的方法来处理上述场景,但这不是本例的重点。)
边缘情况:静态块
如上所述,您可以在静态块中捕获异常。但我们没有提到的是,必须在块内捕获检查异常。静态块没有可以捕获已检查异常的封闭上下文。
边缘案例:lambdas
lambda表达式(通常)不应引发未检查的异常。这并不是对lambdas本身的限制。相反,它是用于提供参数的参数的函数接口的结果。除非函数声明了一个已检查的异常,否则lambda不能抛出一个异常。例如:
List<Path> paths = ...
try {
paths.forEach(p -> Files.delete(p));
} catch (IOException ex) {
// log it ...
}
即使我们似乎已经捕获了IOException
,编译器也会抱怨:
- lambda中有一个未捕获的异常,AND
- CCD_ 38正在捕获从未抛出的异常
事实上,异常需要在lambda本身中捕获:
List<Path> paths = ...
paths.forEach(p -> {
try {
Files.delete(p);
} catch (IOException ex) {
// log it ...
}
}
);
(精明的读者会注意到,在delete
抛出异常的情况下,这两个版本的行为不同…)
更多信息
Oracle Java教程:
- 捕获或指定要求…还包括已检查和未检查的异常
- 捕获和处理异常
- 指定方法引发的异常