正如我们所知,在方法覆盖的情况下如果子类方法抛出一些已检查的异常,那么强制父类方法应抛出相同的已检查异常或其父类异常,否则我们将得到编译错误。但是没有关于未经检查的例外的规则。
但是假设Java允许父类方法检查子类的异常,那么对子类方法检查异常。
有人可以为什么在Java中不允许这样做。
让我们以不同的方式提出问题:
你有A班 -
class A {
public void doStuff() throws SQLException {
}
}
和B类延伸A -
class B extends A {
public void doStuff() throws Exception {
}
}
由于违反了方法的合同,它会在编译期间抛出异常。
假设Java会允许这样做会产生什么后果?
答案 0 :(得分:8)
我想你在问为什么我不能这样做:
class Parent {
void doStuff() throws FileNotFoundException {
}
}
class Child extends Parent {
@Override
void doStuff() throws IOException {
}
}
这很简单,当我这样做时会发生什么:
final Parent parent = new Child();
try {
parent.doStuff();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
而Child
决定抛出一个,说SocketException
?
我正确地尝试catch FileNotFoundException
来自parent.doStuff
,但这不会抓住SocketException
。
我是如何处理未被捕获的已检查的例外情况。这是不允许的。
如果我特意尝试catch SocketException
这将导致编译器错误,因为SocketException
未被声明为从Parent.doStuff
抛出。
在一般情况下解决这个问题的唯一方法是始终catch Exception
,因为任何子类都可以声明它抛出更一般的类型并绕过catch
。这显然不太理想。
答案 1 :(得分:1)
throws
子句是方法签名的一部分,为了覆盖方法,覆盖方法必须与overriden方法具有相同的签名。
因此,如果类B
扩展了类A
并且A
定义了:
public void foo (int param) throws SomeException
{
....
if (something)
throw new SomeException ();
....
}
然后B
只能使用相同的签名覆盖foo:
public void foo (int param) throws SomeException
{
super.foo(param);
....
if (something)
throw new SomeOtherException ();
....
}
SomeOtherException
必须是SomeException
的子类,因为这是方法签名允许抛出的异常类型。如果重写方法会尝试抛出类型为SomeException
的超类,则会违反方法的契约。
让我们声明一些异常类的类层次结构:
SuperException
SomeException
SomeOtherException
AnotherSomeException
您建议的内容(Java allows parent class method to have checked exception which is child to the child class method checked exception
)将允许子类中的重写方法抛出SuperException
类型的任何异常,这将允许它抛出AnotherSomeException
。这违反了方法的签名,因为AnotherSomeException
不是SomeException
的子类。
为了扩展我的评论,让我们考虑如果Java允许您提出建议会发生什么。
假设类C
有一个方法bar
,它接受类A
的实例并调用方法foo
。
public class C
{
....
public void bar (A a)
{
try {
a.foo ();
}
catch (SomeException ex) {
....
}
}
}
由于foo
被声明为抛出SomeException
,因此任何调用foo
的代码都必须处理此异常或声明它可能抛出此异常。现在,如果Java允许B
覆盖foo
,但将其声明更改为public void foo() throws Exception
,foo
中B
的实现可能会抛出任何子类Exception
。由于bar
甚至不知道B
存在,因此它不知道它必须捕获SomeException
及其子类以外的任何异常。因此,编译器不能将此代码标记为错误,但如果bar
的调用者将B
的实例传递给它,则代码将仅在运行时变为无效,因为它可能会抛出一个检查bar
既没有捕获也没有声明的激活。这就是为什么Java覆盖方法时不允许您更改throws
子句的原因。
答案 2 :(得分:1)
如果未在父类中指定,则重写方法不能抛出已检查的异常。原因是,您希望能够在任何可以传递超类型的地方使用子类型的实例,即,您的子类型需要行为,就像您的超类型一样。但是,如果您的子类型可以抛出异常,那么它的行为方式不同:它可以抛出异常。对于未经检查的异常也是如此,但是,正如名称已经告诉我们的那样,它们不会被编译器检查,所以这不是静态强制的。
我建议您查看Liskov substitution principle。
示例说明为什么子类抛出异常而超级类承诺不会抛出异常的原因:
class Animal {
void eatMeat() {System.out.println("eating meat");}
}
class Zebra extends Animal {
void eatMeat() throws IsVegetarianException {throw new IsVegetarianException(); }
}
嗯......也许你的Zebra
不是动物,或Animal
的界面太宽。也许并非所有Animal
都吃肉。顺便说一句:这发生在各种Java集合接口中。他们通过抛出未经检查的NotSupportedException
来解决这个问题,但这只是设计中的一个缺陷,除了改变设计之外,没有其他好的解决方案。
答案 3 :(得分:1)
我可能不正确,但我想你是在问为什么超类A不能扩展继承A类的子类B.
这不可能存在,因为父类是否缺乏明确性。
答案 4 :(得分:0)
如果覆盖没有throw声明的方法,则可以抛出RuntimeException
或子类而不声明。
class A {
public void run() {
}
}
class B extends A {
@Override
public void run() {
throw new RuntimeException("throw without declaration");
}
}
来自the javadoc:
RuntimeException
及其子类是未经检查的异常。 未经检查的异常不需要在方法中声明 构造函数的throws子句,如果它们可以被执行抛出 方法或构造函数并在方法或方法之外传播 构造函数边界。
答案 5 :(得分:0)
因为throws SQLException
实际上意味着“仅 SQLException
”或换句话说除了SQLException
之外不会抛出任何内容
所以,如果你抛出别的东西,你就会违反合同。另一方面,你可以采取其他方式
class A {
public void doStuff() throws Exception {
}
}
class B extends A {
public void doStuff() throws SQLException {
}
}
因为你只是以这种方式签订了更严格的合同。