Java接口和抽象类问题

7

我正在阅读这本书 -- Hadoop权威指南

在第二章(第25页)中提到:“新的API更喜欢抽象类而不是接口,因为这些更容易进化。例如,您可以向抽象类添加一个方法(带有默认实现),而不会破坏该类的旧实现。”这是什么意思(特别是“破坏该类的旧实现”是什么意思)?如果有人能够从这个角度给我展示一个样例,那就感激不尽了。为什么从这个角度看抽象类比接口更好呢?

谢谢, 乔治

3个回答

10
在接口中,实现该接口的类必须实现 所有 定义在接口中的方法。

给定接口 A

interface A {
    public void foo();
}

并且有一个类B:

class B implements A {
}

它必须为接口中定义的方法提供一个实现:

class B implements A {
    @Override
    public void foo() {
        System.out.println("foo");
    }
}
否则它将会是一个编译时错误。现在来看一个带有默认方法实现的抽象类:
abstract class C {
    public void bar() {
        System.out.println("bar");
    }
}

继承自这个抽象类的类可以像这样:

class D extends C { }

代码没有报错。但是如果愿意,它也可以覆盖默认的方法实现。

作者的意思是:如果你的 API 还不稳定,并且需要适应接口(是的,在 OOP 中,抽象类也是接口),那么抽象类允许你添加新功能而不会破坏已经存在的类。然而,这仅对非抽象方法成立。如果你添加抽象方法,则仍然需要在每个派生类中实现它们。但是,如果你有一个仍在发展中并且已经有很多构建在其上的东西的 API,它可以使你的生活更轻松。


谢谢!我困惑的是书中的这个语句 - "breaking old implementations of the class"。 "The class" 指的是派生自抽象类的类,还是抽象类所基于的类? - George2
1
@George:它意味着从旧版本接口/抽象类派生的类。对于接口,它不再有效,因为它未能实现您添加到接口中的方法。对于抽象类和非抽象方法,它可以轻松地使用基类的实现,甚至不知道它存在。 - Joey
谢谢!你的意思是假设在版本1中,抽象类有一个名为Foo的方法,而一个类从抽象类派生并实现了Foo。在版本2中,抽象类新增了一个名为Goo的方法,如果Goo在抽象类中有默认实现,那么派生类仍然可以正常工作而不受影响,是吗? - George2
1
@George:没错。如果你有一个接口(或者将Goo定义为抽象类),那么你就必须遍历每个派生类并添加适当的方法实现。 - Joey
我要补充的是,有些纯OOP派可能会指出存在许多技术和模式来避免这种事情。虽然这可能是真的,但Hadoop的作者们很可能有不使用它们的有效理由。例如,设计模式经常通过灵活性交换实现复杂度。我很高兴能够在一个所有内容都是插件且尽可能保持灵活性的项目上工作。这很麻烦,因为即使是简单的东西也需要花费很长时间来构建。我承认模式确实有其存在的意义,但过度使用亦不可取。 - Joey

6
如果您在抽象类中添加了一个具有默认实现的方法,则任何派生类中不需要进行任何更改。
另外,如果您向接口添加了一个方法,则实现该接口的任何类都需要实现该方法-否则它们将无法编译。

1
该死。我应该把那些例子省略掉的。浪费了我又四分钟 ;-) - Joey
1
@George,Justin:我进行了更改,希望使该部分更清晰,因为“基类”在那里是错误的术语(依我看;或者我可能理解错了)。 - Joey
1
@George - 我的意思是,如果一个抽象类的新方法(带有默认实现)被添加,那么从这个抽象类派生出来的类不需要进行任何改动。 - Justin Ethier
1
@ Johannes - 谢谢!顺便说一下,我觉得我的回答可能太简短了。因为你在更详细地阐述它,所以给你一个+1 :) - Justin Ethier
1
转念一想,我应该为接口/基类扩展的情况提供一个示例,以及为什么这会导致问题...啊,还在学习如何写好 :) - Joey
显示剩余3条评论

1
请参考Eclipse Wiki中的以下指南:
Java API的演变 添加API方法 示例4 - 添加API方法
向类或接口添加API方法会破坏现有客户端的兼容性吗?
如果将方法添加到客户端可能实现的接口中,则肯定是一种破坏性的更改。
如果将方法添加到客户端不允许子类化(实现)的类(接口)中,则不是一种破坏性的更改。
但是,如果将方法添加到客户端可以子类化的类中,则应该通常将更改视为破坏性的。这个严厉的结论的原因是因为客户端的子类已经有了自己的同名方法实现的可能性。将API方法添加到超类会削弱客户端的代码,因为客户端现有的方法符合新添加方法的API契约纯属巧合。实际上,如果这种名称巧合的可能性足够低,这种更改通常被视为非破坏性的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接