覆盖情况下的例外情况

时间:2014-09-27 11:34:31

标签: java

正如我们所知,在方法覆盖的情况下如果子类方法抛出一些已检查的异常,那么强制父类方法应抛出相同的已检查异常或其父类异常,否则我们将得到编译错误。但是没有关于未经检查的例外的规则。

但是假设Java允许父类方法检查子类的异常,那么对子类方法检查异常。

有人可以为什么在Java中不允许这样做。


让我们以不同的方式提出问题:

你有A班 -

class A {

    public void doStuff() throws SQLException {

    }
}

和B类延伸A -

class B extends A {

    public void doStuff() throws Exception {

    }
}

由于违反了方法的合同,它会在编译期间抛出异常。

假设Java会允许这样做会产生什么后果?

6 个答案:

答案 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 ExceptionfooB的实现可能会抛出任何子类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 {

    }
}

因为你只是以这种方式签订了更严格的合同。