为什么Java编译器允许在throws部分中列出异常,该方法无法抛出异常
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么Java编译器允许在throws部分中列出异常,该方法无法抛出异常相关的知识,希望对你有一定的参考价值。
如果有一些代码显然无法抛出异常,那么Java编译器似乎不一致,并且您编写了声明代码可以抛出该异常的周围代码。
请考虑这些代码段。
片段1
从未抛出的异常的catch
。
public void g(){
try {
} catch (FileNotFoundException e) {//any checked exception
}
}
消息是编译错误
Unreachable catch block for FileNotFoundException. This exception is never thrown from the try statement body
Snippet2
一个throws
声明,表示从未抛出的异常。
public void g() throws FileNotFoundException{
}
它汇编很好。
因此,第一个代码段的结果显示编译器可以计算方法是否可以抛出throws
列表中列出的异常。因此,似乎编译器故意不报告第二个片段的错误。但为什么?为什么编译器允许您在throws
部分编写异常,即使编译器知道不能抛出这些异常?
编译器允许这样做,因为方法的throws
子句是方法签名的一部分,而不是其实现的一部分。有可能是implementation might change at some point, while keeping the signature the same。旧的实现可能抛出了已检查的异常,但新的异常可能没有。或者签名的设计者可能希望赋予实现者在不总是必要时抛出已检查异常的灵活性。
试一试!!
对于第二种情况,将来某些其他类可能会覆盖此方法,并且会在其中编写可能引发此异常的代码。
既然你想要其他观点 -
考虑两个不同子类中相同签名的两个实现。
例如,(一个例子),
public class StudentLoader {
public abstract Student readStudentData() throws SQLException, IOException;
public static void main(String args[]) {
StudentLoader loader = getStudentLoader (); //may return any subclass instance
try {
Student s = loader.readStudentData();
}
catch(IOException e) {
//do something
}
catch(SQLException e) {
//do something
}
}
}
public class StudentFileReader extends StudentLoader {
public Student readStudentData() throws IOException {
//read from a file
}
}
public class StudentDBReader extends StudentLoader {
public Student readStudentData() throws SQLException {
//read from DB
}
}
为什么编译器允许在throws部分写入异常,即使它不能被抛出?
即使子类实现StudentDBReader
没有抛出IOException
,父类StudentLoader
仍然必须说throws IOException
,因为StudentLoader
的其他实现可能会抛出它。因此,即使该方法可能不会引发异常,您也可以使用StudentLoader
引用(指向两个子类实例中的任何一个)向调用者指示调用者必须处理这些异常。
在显示方法g()
的代码段1中,没有继承范围。代码就在try块中。如果try中的任何语句抛出一个已检查的Exception,则必须处理它。如果是throws子句,则必须允许继承范围。编译器无法决定在运行时调用哪个版本的readStudentData( )
。
我希望编译器在静态方法的情况下应该给出错误,如果不抛出throws子句中提到的Exception,因为静态方法不参与继承。我不确定为什么静态方法中的throws子句可以包含从不在实现中抛出的异常。它无论如何都不会被覆盖,为什么不在这里抛出错误?我可能会遗漏一些东西。
public class UnreachableCatchBlock {
public static void main(String[] args) {
UnreachableCatchBlock ucb = new UnreachableCatchBlock();
System.out.println(ucb.getClass().getName() + " started.");
// ucb.method1();
ucb.method2();
}
private void method1() {
try {
System.out.println(getClass().getName() + ".method1() started.");
} catch (FileNotFoundException e) {//any checked exception
}
}
private void method2() {
System.out.println(getClass().getName() + ".method2() started.");
}
}
上面的代码编译和运行,如@Raedwald回答中所述。
我不建议将此作为答案,只是发布我试图在评论中包含的代码的地方。
Throws
不处理异常,它表示从调用方法的地方向上抛出异常。换句话说,它只会将异常传递给调用者。
虽然try...catch
块处理异常,这就是为什么Java编译器会检查是否有任何异常来处理catch
块中捕获的异常。
这是两个不同的东西,一个是投掷,另一个是处理异常,编译器会使他的鼻子只在第二个上倾斜......:p
来自JavaDoc:
异常处理程序不仅可以打印错误消息或停止程序。他们可以进行错误恢复,提示用户做出决定,或者使用链式异常将错误传播到更高级别的处理程序。
因此,通过提供try...catch
实现,您要求编译器除了打印异常之外还要做更多的事情。
另一个具体原因:
public void testException() throws FileNotFoundException {
File file = new File("test.txt");
System.out.println(file.exists());
Scanner scanner = new Scanner(file);
}
如果你将通过javap -c Test.class
观察上面例子的编译代码,你会发现将创建一个Exception表。
public static void testException();
Code:
0: new #2 // class java/io/File
3: dup
4: ldc #3 // String test.txt
6: invokespecial #4 // Method java/io/File."<init>":(Ljava/lang/String;)V
9: astore_0
10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: invokevirtual #6 // Method java/io/File.exists:()Z
17: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
20: new #8 // class java/util/Scanner
23: dup
24: aload_0
25: invokespecial #9 // Method java/util/Scanner."<init>":(Ljava/io/File;)V
28: astore_1
29: goto 37
32: astore_1
33: aload_1
34: invokevirtual #11 // Method java/io/FileNotFoundException.printStackTrace:()V
37: return
Exception table:
from to target type
20 29 32 Class java/io/FileNotFoundException
因此,当编译器找不到任何在try块中没有抛出异常的代码时,编译时错误就会出现。
在throws
的情况下,不会生成异常表。
以上是关于为什么Java编译器允许在throws部分中列出异常,该方法无法抛出异常的主要内容,如果未能解决你的问题,请参考以下文章
异常概念和处理机制,try-catch-finally,throw和throws,自定义异常
异常概念和处理机制,try-catch-finally,throw和throws,自定义异常