为什么不能重写比覆盖方法更广泛的exception呢?
我正在阅读Kathe sierra的SCJP 6书,并且遇到了这种以重写方法抛出exception的解释。 我相当不明白 任何人都可以解释给我吗?
重写的方法不能抛出比重写的方法声明的更新或更宽的检查的exception。 例如,声明FileNotFoundException的方法不能由声明SQLException,Exception或任何其他非运行时exception的方法覆盖,除非它是FileNotFoundException的子类。
这意味着如果一个方法声明抛出一个给定的exception,那么子类中的覆盖方法只能声明抛出该exception或其子类。 例如:
class A { public void foo() throws IOException {..} } class B extends A { @Override public void foo() throws SocketException {..} // allowed @Override public void foo() throws SQLException {..} // NOT allowed }
SocketException extends IOException
,但是SQLException
不会。
这是因为多态性:
A a = new B(); try { a.foo(); } catch (IOException ex) { // forced to catch this by the compiler }
如果B
已经决定抛出SQLException
,那么编译器不会强迫你去捕捉它,因为你是通过它的超类–A来引用B
的实例的。 另一方面, IOException
任何子类都将由处理IOException
的子句(catch或throws)处理
你需要能够通过它们的超类来引用对象的规则是Liskovreplace原则。
由于未经检查的exception可以在任何地方抛出,因此它们不受此规则的约束。 如果需要,可以将一个未经检查的exception作为文档的一种forms添加到throws子句中,但是编译器不会执行任何事情。
重写方法CAN会抛出任何未经检查的(运行时)exception,无论重写的方法是否声明exception
例:
class Super { public void test() { System.out.println("Super.test()"); } } class Sub extends Super { @Override public void test() throws IndexOutOfBoundsException { // Method can throw any Unchecked Exception System.out.println("Sub.test()"); } } class Sub2 extends Sub { @Override public void test() throws ArrayIndexOutOfBoundsException { // Any Unchecked Exception System.out.println("Sub2.test()"); } } class Sub3 extends Sub2 { @Override public void test() { // Any Unchecked Exception or no exception System.out.println("Sub3.test()"); } } class Sub4 extends Sub2 { @Override public void test() throws AssertionError { // Unchecked Exception IS-A RuntimeException or IS-A Error System.out.println("Sub4.test()"); } }
在我看来,这是Java语法devise中的一个失败。 多态性不应限制exception处理的使用。 事实上,其他计算机语言不这样做(C#)。
而且,一个方法在一个更专门化的子类中被覆盖,使得它更复杂,因此更可能抛出新的exception。
为了说明这一点,请考虑:
public interface FileOperation { void perform(File file) throws FileNotFoundException; } public class OpenOnly implements FileOperation { void perform(File file) throws FileNotFoundException { FileReader r = new FileReader(file); } }
假设你写道:
public class OpenClose implements FileOperation { void perform(File file) throws FileNotFoundException { FileReader r = new FileReader(file); r.close(); } }
这会给你一个编译错误,因为r.close()抛出一个比FileNotFoundException更广泛的IOException。
要解决这个问题,如果你写:
public class OpenClose implements FileOperation { void perform(File file) throws IOException { FileReader r = new FileReader(file); r.close(); } }
你将得到一个不同的编译错误,因为你正在执行perform(…)操作,但是抛出一个未包含在该方法的接口定义中的exception。
为什么这很重要? 那么界面的用户可能有:
FileOperation op = ...; try { op.perform(file); } catch (FileNotFoundException x) { log(...); }
如果允许抛出IOException,则客户端的代码不再正确。
请注意,如果使用未经检查的exception,则可以避免此类问题。 (我不是暗示你做或不做,这是一个哲学问题)
我在这里提供了这个答案,老问题,因为没有答案告诉重写的方法可以抛出什么这里再次覆盖的方法可以抛出的事实:
1)抛出相同的exception
public static class A { public void m1() throws IOException { System.out.println("A m1"); } } public static class B extends A { @Override public void m1() throws IOException { System.out.println("B m1"); } }
2)抛出overriden方法的抛出exception的子类
public static class A { public void m2() throws Exception { System.out.println("A m2"); } } public static class B extends A { @Override public void m2() throws IOException { System.out.println("B m2"); } }
3)什么也不扔。
public static class A { public void m3() throws IOException { System.out.println("A m3"); } } public static class B extends A { @Override public void m3() //throws NOTHING { System.out.println("B m3"); } }
4)抛出RuntimeExceptions不是必需的。
抛出或不抛出RuntimeExceptions,编译器不会抱怨它。 RuntimeExceptions不是检查exception。 只有检查exception才需要出现在抛出,如果没有捕获。
假设你有超A类方法M1 throwin E1和B从A方法派生M2方法覆盖M1。 M2不能抛出任何不同于E1的东西。
因为多态性,使用类A的客户端应该能够把B看作是A。Inharitance ===> Is-a(B is-a A)。 如果这个处理类A的代码正在处理exceptionE1,如M1声明它抛出这个检查的exception,然后抛出不同types的exception呢? 如果M1抛出IOExceptionexceptionM2可能抛出FileNotFoundExceptionexception,因为它是一个IOException。 A的客户可以处理这个没有问题。 如果抛出的exception更广泛,A的客户就不会有机会了解这一点,因此就没有机会去捕捉它。
重写的方法不能抛出比重写的方法声明的更新或更宽的检查的exception。
例:
class Super { public void throwCheckedExceptionMethod() throws IOException { FileReader r = new FileReader(new File("aFile.txt")); r.close(); } } class Sub extends Super { @Override public void throwCheckedExceptionMethod() throws FileNotFoundException { // FileNotFoundException extends IOException FileReader r = new FileReader(new File("afile.txt")); try { // close() method throws IOException (that is unhandled) r.close(); } catch (IOException e) { } } } class Sub2 extends Sub { @Override public void throwCheckedExceptionMethod() { // Overriding method can throw no exception } }
重写的方法不能抛出比重写的方法声明的更新或更宽的检查的exception。
这只是意味着当你重写一个已经存在的方法时,这个重载的方法抛出的exception应该是原始方法抛出的exception或者它的任何子类 。
请注意,检查是否所有检查的exception都是在编译时完成的,而不是在运行时完成的。 所以在编译时本身,Java编译器会检查重写方法抛出的exception的types。 由于哪个重写的方法会被执行,只能在运行时才能决定,所以我们不能知道我们必须捕捉什么types的exception。
例
假设我们有A
类和它的B
类。 A
有一个方法m1
和class B
已经覆盖了这个方法(让我们称它m2
来避免混淆..)。 现在我们假设m1
抛出E1
,而m2
抛出E2
,这是E1
的超类。 现在我们写下面这段代码:
A myAObj = new B(); myAObj.m1();
请注意, m1
只不过是对m2
的调用(同样,方法签名在重载的方法中也是一样的,所以不要和m1
和m2
混淆。它们只是为了区分这个例子…它们都有相同的签名)。 但是在编译时,所有java编译器所做的都是引用types(在这种情况下,类A
)检查方法是否存在,并期望程序员处理它。 很明显,你会扔或抓E1
。 现在,在运行时,如果重载的方法抛出E2
是E1
的超类,那么……这是非常错误的(出于同样的原因,我们不能说B myBObj = new A()
)。 因此,Java不允许它。 重载的方法抛出的未经检查的exception必须是相同的,子类的或不存在的。
那么java.lang.Exceptioninheritancejava.lang.Throwable。 java.io.FileNotFoundExceptioninheritancejava.lang.Exception。 所以如果一个方法抛出java.io.FileNotFoundException,那么在override方法中,不能抛出层次比FileNotFoundException更高的任何东西,比如你不能抛出java.lang.Exception。 你可以抛出FileNotFoundException的子类。 但是你将被迫在overriden方法中处理FileNotFoundException。 敲一些代码,试试吧!
规则在那里,所以你不要因为拓宽特殊性而失去原始的抛出声明,因为多态意味着你可以在超类上调用overriden方法。
我们对下面的解释是什么?
class BaseClass { public void print() { System.out.println("In Parent Class , Print Method"); } public static void display() { System.out.println("In Parent Class, Display Method"); } } class DerivedClass extends BaseClass { public void print() throws Exception { System.out.println("In Derived Class, Print Method"); } public static void display() { System.out.println("In Derived Class, Display Method"); } }
类DerivedClass.java抛出一个编译时exception,当print方法抛出Exception时,基类的print()方法不抛出任何exception
我可以将此归因于Exception比RuntimeException更窄的事实,它可以是无例外(运行时错误),RuntimeException及其子例外