"error: unreported exception <XXX>; must be caught or declared to be thrown"
其中XXX是某个异常类的名称。
请解释:
- 编译错误信息表明了什么,
- 这个错误背后的Java概念,以及
- 如何修复它。
"error: unreported exception <XXX>; must be caught or declared to be thrown"
其中XXX是某个异常类的名称。
请解释:
首先要明确的是,这是编译错误而不是异常。你应该在编译时看到它。
如果你在运行时看到了它出现在异常信息中,那很可能是因为你正在运行一些存在编译错误的代码。回去修复编译错误,然后找到并设置你的IDE中的选项,以防止它为存在编译错误的源代码生成“.class”文件。(节省未来的痛苦)
回答这个问题的简短答案是:
错误信息提示该语句抛出(或传播)了一个已检查异常,但是异常(XXX
)没有被正确处理。
解决方法是通过以下方式之一处理异常:
try…catch
语句捕获并处理异常,或者throws
它1。1- 在某些边缘情况下可能无法这样做。请阅读其他答案!
在Java中,异常由继承自java.lang.Throwable
类的类表示。异常分为两类:
Throwable
、Exception
及其子类,除了RuntimeException
及其子类。Error
及其子类和RuntimeException
及其子类。(在上述情况中,“子类”包括直接和间接子类。)
已检查异常和未检查异常之间的区别在于,必须在发生异常的封闭方法或构造函数内处理已检查异常,但无需处理未检查异常。
(问题:如何确定异常是否已检查?答案:找到异常类的javadoc,并查看其父类。)
从Java语言的角度来看,有两种处理异常的方式可以“满足”编译器:
你可以在try ... catch
语句中捕获异常。例如:public void doThings() {
try {
// 做一些事情
if (someFlag) {
throw new IOException("无法读取某些内容");
}
// 做更多的事情
} catch (IOException ex) {
// 处理它 <<<=== 在这里
}
}
IOException
的语句放在了 try
的主体中。然后,我们编写了一个 catch
子句来捕获异常。(我们可以捕获 IOException
的超类...但在这种情况下,那将是 Exception
,而捕获 Exception
是不好的做法。)throws
异常:public void doThings() throws IOException {
// 做一些事情
if (someFlag) {
throw new IOException("无法读取某些内容");
}
// 做更多的事情
}
doThings()
抛出 IOException
。这意味着调用 doThings()
方法的任何代码都必须处理异常。简而言之,我们将处理异常的问题传递给了调用者。
这些事情中哪一个是正确的做法?
这取决于上下文。然而,一个通用原则是,您应该在能够适当地处理异常的代码级别上处理异常。这又取决于异常处理代码将要执行的操作(在此处
)。它能够恢复吗?它可以放弃当前请求吗?它应该停止应用程序吗?
回顾一下。编译错误意味着:
您的解决方案应该是:
考虑来自这个问答的以下示例
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 Exception
。这会强制调用者处理(潜在的)任何已检查异常...这是一场噩梦。
不要压制异常。例如:
try {
...
} catch (NullPointerException ex) {
// It never happens ... ignoring this
}
如果你压制异常,你可能会使触发它们的运行时错误更难诊断。你正在销毁证据。
注意:仅仅相信异常永远不会发生(根据注释)并不一定是事实。
有些情况下,处理已检查异常是一个问题。其中一个特殊情况是在static
初始化器中处理已检查异常。例如:
private static final FileInputStream input = new FileInputStream("foo.txt");
FileInputStream
被声明为 throws FileNotFoundException
... 这是一个已检查的异常。但由于上述是字段声明,Java 语言的语法不允许我们将声明放在 try
... catch
中。而且没有适当的(封闭的)方法或构造函数 ... 因为这段代码是在 类 初始化时运行的。
一种解决方案是使用 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'
}
(在实际代码中,有更好的处理上述情况的方法,但这不是本例的重点。)
如上所述,您可以在静态块中捕获异常。但我们没有提到的是,您必须在块内部捕获已检查的异常。静态块没有包含上下文可以捕获已检查的异常。
Lambda表达式(通常)不应抛出未经检查的异常。这不是对Lambda表达式本身的限制。而是由于用于参数的函数接口的后果,在您提供参数的地方。除非函数声明了已检查的异常,否则Lambda不能抛出异常。例如:
List<Path> paths = ...
try {
paths.forEach(p -> Files.delete(p));
} catch (IOException ex) {
// log it ...
}
尽管我们似乎已经捕获了IOException
,但编译器会抱怨:
catch
捕获了从未抛出的异常!实际上,异常需要在lambda表达式本身中捕获:
List<Path> paths = ...
paths.forEach(p -> {
try {
Files.delete(p);
} catch (IOException ex) {
// log it ...
}
}
);
(敏锐的读者会注意到,在delete
抛出异常的情况下,这两个版本的行为是不同的...)
Oracle Java 教程: