Java语言提供了两种机制,可以用来定义一个允许多个实现的类型:接口和抽象类。如果对它们的基本定义还不是很清楚请参考其它书籍。
接口和抽象类这二者比较起来,从特征来说,最明显的区别在于抽象类允许包含某些方法的实现,但是接口是不允许的。但是在使用方面还有一个更为突出的区别,就是抽象类是通过被子类继承来使用的;而接口是通过被实现来使用的。而Java是一种单继承机制的语言,所以就限制了抽象类的使用。(一个类只有一个超类或叫父类)当一个类已经继承了一个超类时,现在你再想因为一些原因让它再继承一个抽象类是不可能的(当然也有变通办法,就是先让这个抽象类继承那个超类,再把子类的超类改为这个抽象类。即原超类和子类的关系从父子关系变为了爷孙关系,中间插了这个抽象类)。这时候就能体现出接口的优势了——一个类可以实现多个接口(其实这也是Java之所以大胆地放弃多继承的原因)。
再来罗列一下使用接口的其他好处吧:
1)已有的类可以很容易被更新,以实现新的接口。
其实这就是上面说的,如果是使用抽象类就麻烦了。
2)接口是定义混合类型的理想选择。
3)接口使得我们可以构造出非层次结构的类型框架。
层次结构用来描述组织机构这样的事物是非常合适的。但并不是所有事物都适用。
举一个汽车的例子:
【继承的思想】: 车 — 汽车 — 广义乘用车 — 轿车 — 小轿车 — 大众品牌小轿车 — 捷达车 — 捷达出租车
这样的继承,直到“小轿车”之前还是合适的,从意义来说,这些类都应该是抽象类,因为无法实例化一个“小轿车”,它只是一类车的统称,只有某一辆,比如:京 B12345牌照号的(或者更严谨地用车架号)具体车才应该可以实例化。
然而到了“大众品牌小轿车”之后就不合适了。显然,“大众品牌”不仅仅有“小轿车”,“捷达车”也不仅仅有“车租车”。这时候就该使用接口了。
public class MyCar extends 小轿车 implements 大众品牌, 捷达牌, 车租车able {
public MyCar(String sn){
.....
}
}
4)接口使得安全地增强一个类的功能成为可能
方法是使用包装类模式。我个人并不喜欢这样的做法,就不细说了。
说了这么多接口的好处,那么抽象类是一无是处的吗?当然不是。一个明显的优势是,抽象类的演化(版本更新)比接口容易的多。如果在后续版本中你希望在抽象类中增加一个新方法,那么你直接增加就好了。你其实就是在抽象类中给出了一个默认的实现而已,子类会自然地继承这个方法,子类可以自由选择是否改写这个方法。
接口就不行了。一旦发行你就不能在增加新的方法的定义了(还记得之前提到过几次的“你有义务保持向前兼容”吗?)。如果你增加了新的方法的定义,那么所有实现了此接口的类就统统要增加对此方法的实现,如果你的接口已经发布,那么这几乎是不可能的(除非只有你自己用你之前版本的发行包)。
关于骨架实现类:在选择抽象类和接口时,并不是二选一的答案,或干脆枪毙掉抽象类。其实,你可以把接口和抽象类的优点结合起来,对于你希望导出(对外提供)的每一个重要接口都提供一个抽象类(骨架实现类)。接口的作用仍然是定义类型,骨架实现类负责所有与接口实现相关的工作。
按照惯例,骨架实现类的命名方法为: AbstractInterface,这里的Interface指的是接口的名字。建议你也去读读AbstractList、AbstractMap的源码,或许有不少启发。(我也要去再读读...)
【Effective Java 学习笔记】系列连载专题请见:
http://tonylian.iteye.com/categories/64208