本文所有绝大部分内容都提取自《Java编程思想》第8章。
最近想复习一下Java中多态这个概念,就在网上找了十几篇文章来看,看完之后还是感觉一头雾水。只好老老实实去看《Java编程思想》,看完之后不能说醍醐灌顶吧,但确实对多态这个概念有了更全面的认识。明白了为什么会有多态以及多态的用途,而不单单只是一个让我死记硬背的概念。
Q:什么是多态?
A: 先看一段代码
//有三个音符的乐谱
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT;
}
//能演奏出音符的乐器
class Instrument {
public void play(Note n) {
print("Instrument.play" + n);
}
}
//能演奏出音符的wind乐器
public class Wind extends Instrument {
public void play(Note n) {
System.out.println("Wind.play()" + n);
}
}
//能演奏出音符的Brass乐器
public class Brass extends Instrument {
public void play(Note n) {
System.out.println("Brass.play()" + n);
}
}
//Music测试类
public class Music {
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
Brass frenchHorn = new Brass();
tune(flute);
tune(frenchHorn);
}
}/* Output:
Wind.play() MIDDLE_C
Brass.play() MIDDLE_C
*/
请观察一下Music测试类中的tune方法,它接受一个Instrument引用。那么在这种情况下,编译器怎样才能知道这个Instrument引用指向的是Wind对象,而不是Brass对象?实际上,编译器无法得知。
在此之前让我们了解一下绑定。将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定,叫做前期绑定。这在面向过程的语言中不需要选择就默认的绑定方式。例如,C只有一种方法调用,那就是前期绑定。
上述程序之所以令人迷惑,主要是因为前期绑定。因为,当编译器只有一个Instrument引用,它无法知道究竟调用哪一个方法才对。
解决办法就是后期绑定,它的含义就是在运行时根据对象的类型进行绑定。后期绑定也也叫做动态绑定或运行时绑定。如果一种语言想实现后期绑定,就必须具有某种机制,以便在运行时判断对象的类型,从而调用恰当的方法。
Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。这意味着通常情况下,我们不必判定是否进行后期绑定——它会自动发生。
因此,我们可以说多态是一种帮助我们实现了在运行时根据对象的类型进行绑定的机制。
多态的缺陷
一旦你了解多态机制,可能就会开始认为所有事物都可以多态地发生。然而,只有普通的方法调用可以是多态的。如果某个方法是静态的,它的行为就不具有多态性。(静态方法在类加载到内存的时候执行并且只执行一次,因此动态绑定也无从谈起。)静态方法是与类,并非与单个对象关联的。
继承拥有了多态,但还是敌不过组合
学习了多态之后,看起来似乎所有东西都可以被继承,因为多态是一种如此巧妙地工具。事实上,当我们使用现成的类来建立新类时,如果首先考虑继承技术,反而会加重我们的设计负担,使事情变得不必要地复杂起来。
更好地方式是首先选择“组合”,尤其是不能十分确定应该使用哪一种方式时。下面举例说明了这一点:
class Actor {
public void act() {}
}
class HappyActor extends Actor {
public void act { print("HappyActor"); }
}
class SadActor extends Actor {
public void act { print("SadActor"); }
}
class Stage {
private Actor actor = new HappyActor();
public void change { actor = new SadActor(); }
public void performPlay() { actor.act(); }
}
public class Transmogrify {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performly();
stage.change();
stage.performPlay();
}
}/*Output:
HappyActor
SadActor
*/
在这里,Stage对象包含一个对Actor的引用,而Actor被初始化为HappyActor对象。既然引用在运行时可以与另一个不同的对象重新绑定起来,所以SadActor对象的引用可以在actor中被替代,然后由performPlay()产生的行为也随之改变。这样一来,我们在运行期间就获得了动态灵活性(这也称作状态模式)。与此相反,我们不能在运行期间决定继承不同的对象,因为它要求在编译期间完全确定下来。
一条通用的准则:“用继承表达行为间的差异,并用字段表达状态上的变化”。在上述例子中,两者都用到了:通过继承得到两个不同的类,用于表达act()方法的差异;而Stage通过运用组合使自己的状态发生变化。
总结
在面向对象的程序设计语言中,多态是封装和继承之后的第三种基本特征。
多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来,作用是消除类型之间的耦合关系。
继承允许将对象视作它自己本身的类型或其父类型来加以处理。这种能力极为重要,因为它允许将多种类型(继承于同一个父类的)视作同一类型来处理,而同一份代码可以毫无差别地运行在这些不同类型之上了。多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是继承于同一个父类。
如果不运用封装和继承,就不可能理解或者不可能创建多态的例子。多态是一种不能单独来看待的特性,相反它只能作为类关系“全景”中的一部分,与其他特性协同工作。
参考:
- 《Java编程思想》