在Java中,抛出检查异常的方法(异常或它的子类型-IOExtRebug、中断ExtExcCEP等)必须声明抛出语句:
不声明throws语句的方法不能抛出已检查的异常。
1 2 3 4
| public int read () { // does not compile
throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown |
但是在Java中捕获安全方法中的检查异常仍然是合法的:
1 2 3 4 5 6 7 8 9
| 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不是一个检查过的异常,并允许重新执行它。事情甚至有点荒谬,这段代码不会编译:
1 2 3 4 5 6 7 8
| 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会发生什么潜在的错误?我们非常欣赏这些例子。
- 关于主要问题:它本身不是设计中的一个缺陷,RuntimeExceptions是异常的子类,因此捕获异常也包括未检查的异常。也就是说,没有理由这样做,它甚至可能会使阅读代码的人感到困惑,因为他们可能认为safemethod()可能会抛出异常。我认为在这里捕获runtimeexception是更好的选择。
- 关于throw语句的jls的相关部分。
- 你甚至可以抓住Throwable。抓一个更普通的类型有什么问题?
- @如果我们可以使用原始类型,那么为什么要使用泛型呢?答案是更强的编译器检查。编译器可以告诉我们不能抛出异常,但只能抛出RuntimeException,那么为什么要捕获它呢?
- @Adamskywalker我们知道原始类型导致的许多问题。更广泛的类型会导致什么问题?这就是你的比喻失败的原因。根据您的论点,final Object ob ="foo";也会导致编译器错误,因为我们知道在编译时ob的运行时类型将是String。
- @我同意这一点。
- 因为safeMethod()是安全的,这就意味着被抓到的Exception e必须是RuntimeException。如果它保持原样(如第一个片段中所示),那么一切都很好。但是,当您在第二个代码片段中显式地强制转换到Exception时,您会使编译器忘记它知道的内容,并相信它可能是任何Exception,当然这是不正常的。
引用Java语言规范,{112.3:
It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case that the try block corresponding to the catch clause can throw a checked exception class that is a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception.
我猜想这个规则早在Java 7之前就已经出现了,在那里没有多捕获。因此,如果您有一个可以抛出许多异常的try块,那么捕获所有内容的最简单方法就是捕获一个公共超类(在最坏的情况下,如果您还想捕获Error,则为Exception或Throwable)。
请注意,您可能无法捕获与实际抛出的内容完全无关的异常类型-在您的示例中,捕获不是RuntimeException的Throwable的任何子类将是一个错误:
1 2 3 4 5
| try {
System. out. println("hello");
} catch (IOException e ) { // compilation error
e. printStackTrace();
} |
按op编辑:答案的主要部分是问题示例仅适用于异常类。通常,不允许在代码的随机位置捕获选中的异常。抱歉,如果我用这些例子把别人搞糊涂了。
- 确切地说,这个规则早在尝试多重捕获之前就存在了,可能是从1.0开始的(但肯定是从1.2开始的)。
- 抓到Exception并不能抓住一切。抓Throwable什么都抓。
- 您给出了一个包含多个异常的示例。如果抛出了多个异常并且至少检查了其中一个异常,我可以捕获公共父异常。但对于所有未检查的异常,都有一个公共的父级-runtimeexception。所以我仍然可以用一个catch子句处理多个未检查的异常
- @Adamskywalker规则很简单:如果可以抛出异常E,则可以捕获E的任何超类。为什么这个规则在历史上是必要的,在答案中已经明确说明了。你所建议的问题是,这些规则对于解决一个根本不存在的问题会变得更加混乱和复杂。
- Adamskywalker:是的,但是语言设计者/编译器作者也需要避免事情变得比必要的更复杂。正如@biziclop所建议的,您所建议的修改将解决一个非常小的问题,但会使规则及其实现变得复杂。
- @廷宾德:我想把重点放在例外上,但我同意我最初的措辞让它听起来像例外是唯一可以抛出的东西。我已经编辑了我的答案。
- @Adamskywalker:而且,由于任何东西都可以抛出一个Error,所以必须始终允许捕获Throwable,因此规则看起来更加复杂:"您可以捕获所有可能抛出的异常类型的任何子类,并且可以捕获Throwable的最低公共祖先。"-vs"您可以捕获可能抛出的任何子类或超类。"
Java 7引入了更具包容性的异常类型检查。
However, in Java SE 7, you can specify the exception types FirstException and SecondException in the throws clause in the rethrowException method declaration. The Java SE 7 compiler can determine that the exception thrown by the statement throw e must have come from the try block, and the only exceptions thrown by the try block can be FirstException and SecondException.
本文讨论的是一个try块,专门抛出FirstException和SecondException;虽然catch块抛出Exception,但方法只需声明它抛出FirstException和SecondException,而不是Exception:
1 2 3 4 5 6 7 8 9
| public void rethrowException (String exceptionName )
throws FirstException, SecondException {
try {
// ...
}
catch (Exception e ) {
throw e ;
}
} |
这意味着编译器可以检测到在test中抛出的唯一可能的异常类型是Errors或RuntimeExceptions,两者都不需要捕获。当您使用throw e;时,即使静态类型是Exception时,它也可以告诉您不需要声明或重新捕获。
但是当你把它投射到Exception时,这就绕过了这个逻辑。现在,编译器将其视为需要捕获或声明的普通Exception。
将此逻辑添加到编译器中的主要原因是,当重新调用捕获这些特定子类型的通用Exception时,程序员只允许在throws子句中指定特定的子类型。但是,在这种情况下,它允许您捕获一个通用的Exception,而不必在throws子句中声明任何异常,因为没有可以抛出的特定类型是检查异常的。
- 我理解为什么第一个代码编译而第二个代码不编译。我想我应该用粗体字把主要问题括起来。
- 主要问题在标题中——如果我们知道不能抛出异常,为什么还要允许捕获它呢?
- 在语义层面上,我不相信这能完全解释正在发生的事情。是的,它描述了行为,但我不认为这类事情才是真正的答案。
- @AdamskyWalker总是可以捕获所有可能抛出的异常的超类,例如,想象一个文件操作方法,它包装了可以抛出FileNotFoundException和IIOException的代码。在try-multi-catch之前,处理这两个问题的唯一方法是要么有两个相同的catch条款,要么抓住它的一个超类,例如IOException。或Exception。这两种解决方案在某种程度上都是不好的,但第二种方案则稍差一点。
- 可以抛出类型Exception。外面有很多坏代码,它们的方法只是声明throws Exception。可以构造和抛出异常本身,而无需进一步对其进行子类化。像Throwable一样,所有可以抛出的东西的超级类型。
- @Biziclop我写了我对超类捕捉的想法,回答是
- @还有一些相当合理的案例,比如反思。
这里的问题是,选中/未选中的异常限制会影响代码允许抛出的内容,而不是允许捕获的内容。虽然您仍然可以捕获任何类型的Exception,但只有未选中的类型才允许您再次实际抛出。(这就是将未选中的异常强制转换为选中的异常会破坏代码的原因。)
用Exception捕获未检查的异常是有效的,因为未检查的异常(a.k.a.RuntimeExceptions)是异常的一个子类,并且遵循标准的多态性规则;它不会将捕获的异常转换为Exception,就像在Object中存储String不会将String转换为EDOCX1一样。〔30〕。多态性意味着一个可以保存Object的变量可以保存来自Object的任何东西(例如String)。同样,由于Exception是所有异常类型的超类,因此,Exception类型的变量可以保存从Exception派生的任何类,而不必将对象转换为Exception类型。考虑一下:
1 2 3 4 5
| import java.lang.*;
// ...
public String iReturnAString () { return"Consider this!"; }
// ...
Object o = iReturnAString (); |
尽管变量的类型是Object,但o仍然存储String,不是吗?同样,在您的代码中:
1 2 3 4 5
| try {
safeMethod ();
} catch (Exception e ) { // catching checked exception
throw e ; // so I can throw... a checked Exception?
} |
实际上这意味着"捕获任何与类EDCOX1,4"兼容的任何东西(即EDCOX1,4和它派生的任何东西)。同样的逻辑也在其他语言中使用;例如,在C++中,捕获一个EDCOX1,45个也将捕获EDCOX1,46,EDCOX1,47,EDCOX1,48,任何适当定义的用户创建的异常,等等。n,因为它们都来自于std::exception。
tl;dr:你没有捕捉到选中的异常,你捕捉到了任何异常。只有将异常转换为选中的异常类型时,该异常才会成为选中的异常。
- @阿达姆斯基沃克,请不要轻率地对待那些只想帮助你的人。多态性的观点是完全正确的,因为它的核心是catch (Exception ex)与Exception ex = ...非常相似。