2

If I have an abstract class with the following function -

abstract class A{
    void foo(String s) throws Exception{
        throw new Exception("exception!");
    }
}

And then another class that extends the abstract class and implements its own version of foo -

class B extends A{
    void foo(String s){
        //do stuff that does *not* throw an exception
    }
}

Will this create problems? Specifically in the following test case -

Collection<A> col = new Collection<A>();
B b = new B();
col.add(b);
for(A a : col){
    a.foo();
}

I did some testing and nothing seems to have broken, but I don't understand why B's foo was called and not A's

4

4 回答 4

11

因为Polymorphism.

因为,在运行时,实际对象的类型CollectionB这样的,所以B.foo()被调用了。

基本上,如果您有一个子类型对象分配给超类引用,则运行时多态性确保实例方法的子类型版本被调用,即,如果它当然已被覆盖。如果不是,则调用将退回到超类版本。

什么是有效的方法覆盖?

被覆盖的方法必须有

  • 相同的方法签名
  • 协变返回类型(可以返回子类型)
  • 不得抛出更广泛的检查异常 (适用于您的问题和@Dgrin91 的评论,即仅仅因为被覆盖的方法承担了一些风险(抛出异常)并不意味着覆盖方法应该做同样的事情;因此,它可能不会在全部)
  • 不得使用限制较少的访问修饰符(可以将保护设为公共但不能设为私有)
于 2013-06-11T20:24:10.963 回答
3

这不是问题——事实上,常见的做法是在未实现功能的基类中抛出异常,然后用未抛出的东西覆盖实现。一旦你重写了一个方法,基类的方法就不会被调用。

这样做的一个缺点是,如果它恰好是已检查的(而不是“运行时”)类型,用户将需要捕获您的异常。一个常见的解决方案是抛出未经检查的异常。

当然如果抛出异常的唯一目的是表明功能没有实现,最好在对应的方法上做标记abstract,让Java编译器捕捉到可能的违规行为。

于 2013-06-11T20:26:20.867 回答
0

您可以在不声明 throws 的情况下覆盖方法。这对于使用具体类的调用者很有用,例如使用 B 类的人不需要尝试捕获,因为该方法的实现不会抛出任何东西。

Jon Skeet 在这里提供了更详细的解释:继承、方法签名、方法覆盖和抛出子句

于 2013-06-11T20:48:19.450 回答
0

正如另一张海报所指出的,这种行为是由于多态性而发生的。

您的集合被声明为 A.B 的元素,声明为扩展 A,是 A。您可以将 B 类型的元素添加到集合(需要 A 的实例)这一事实证实了这一点。

您的 A.foo 实现会引发异常,并且如果已调用它,则确实会引发异常。另一方面,B 覆盖方法 foo,不抛出任何异常。由于您添加到集合中的实例是 B 之一,因此 B.foo 会被调用。它不会改变您的 for 循环将实例声明为 A 类型(这是有效的,因为 B 是-a A)。

您观察到的行为是预期的行为。

为了更好地理解,您可能需要创建:

class C extends A {}

并将一个 C 实例添加到集合中。迭代 C 的 foo 将委托给父类 (A),然后按预期抛出异常。

于 2013-06-11T20:31:12.733 回答