SCJP – 具有exception处理的覆盖方法会引发编译器错误

在Kathey Sierra的SCJP书中,摘录如下:


如果重写了某个方法,但是您使用多态(超类型)引用来使用重写方法引用子类型对象,则编译器会假定您正在调用该方法的超类型版本。 如果超类型版本声明了一个已检查的exception,但是重写的子类型方法没有,则编译器仍然认为您正在调用一个声明exception的方法(在第5章中有更多内容)。

我们来看一个例子:

class Animal { public void eat() throws Exception { // throws an Exception } } class Dog2 extends Animal { public void eat() { /* no Exceptions */ } public static void main(String[] args) { Animal a = new Dog2(); Dog2 d = new Dog2(); d.eat(); // ok a.eat(); // compiler error - // unreported exception } } 

由于在Animal eat()方法上声明了Exception,因此无法编译此代码。 即使在运行时使用的eat()方法是Dog版本,也没有声明exception,就会发生这种情况。


现在我不明白的是a.eat(); 引发编译错误? (即使在Super的情况下,子项中的重写函数也可能没有任何exception)

编译器不知道引用的对象的实际类型。 它仅检查分配是否有效。

a.eat的调用会导致编译错误,因为编译器不知道a引用的Animal是Dog2。 它决定是否可以仅根据引用Dog2的变量的类型抛出已检查的exception。

编译器不聪明。 它不运行代码,也不跟踪分配给变量a的对象的实际类。

正如您引用具有其超类类型的对象时,您将看不到特定于子类的方法,您还会看到超类的throws-clause,而不是子类的方法。

如果在最后一行中向子类添加强制转换:

 ((Dog2)a).eat(); 

那么你将不会得到未报告的exception编译错误。

当你查看代码时,你可以意识到:当你调用eat()on的对象是

  • 一只狗,然后通话不会抛出exception
  • 一个动物,然后那个召唤可以扔掉

因此,第二次使用会导致编译器向您抱怨。

覆盖方法并减少抛出签名是完全正常的。 当子类永不抛出时,知道如何处理exception的调用者确实有效。