Java中的多态性

来源:互联网 发布:mac如何连接远程服务 编辑:程序博客网 时间:2024/05/19 15:39

1、上溯造型

之前我们已经知道将一个对象作为它自己的类型使用,或者作为它的基础类型的一个对象使用。取得一个对象句柄,并将其作为基础类型句柄使用的行为就叫作“上溯造型”——因为继承树的画法是基础类位于最上方。但这样做也会遇到一个问题,如下例所示:
//Inheritance & upcastingclass Note {private int value;private Note(int val) {value = val;}public static final Note middleC = new Note(0), cSharp = new Note(1), cFlat = new Note(2);} // Etc.class Instrument {public void play(Note n) {System.out.println("Instrument.play()");}}// Wind objects are instruments// because they have the same interface:class Wind extends Instrument {// Redefine interface method:public void play(Note n) {System.out.println("Wind.play()");}}public class Music {public static void tune(Instrument i) {// ...i.play(Note.middleC);}public static void main(String[] args) {Wind flute = new Wind();tune(flute); // Upcasting}}

输出结果:
Wind.play()

其中,方法Music.tune()接收一个Instrument 句柄,同时也接收从Instrument 衍生出来的所有东西。当一个Wind 句柄传递给tune()的时候,就会出现这种情况。

2、为什么要上溯造型

如果让tune()简单地取得一个Wind 句柄,将其作为自己的自变量使用,似乎会更加简单、直观得多。但要注意:假如那样做,就需为系统内Instrument 的每种类型写一个全新的tune()。假设按照前面的推论,加入Stringed(弦乐)和Brass(铜管)这两种Instrument(乐器):
//Overloading instead of upcastingclass Note2 {private int value;private Note2(int val) {value = val;}public static final Note2 middleC = new Note2(0), cSharp = new Note2(1), cFlat = new Note2(2);} // Etc.class Instrument2 {public void play(Note2 n) {System.out.println("Instrument2.play()");}}class Wind2 extends Instrument2 {public void play(Note2 n) {System.out.println("Wind2.play()");}}class Stringed2 extends Instrument2 {public void play(Note2 n) {System.out.println("Stringed2.play()");}}class Brass2 extends Instrument2 {public void play(Note2 n) {System.out.println("Brass2.play()");}}public class Music2 {public static void tune(Wind2 i) {i.play(Note2.middleC);}public static void tune(Stringed2 i) {i.play(Note2.middleC);}public static void tune(Brass2 i) {i.play(Note2.middleC);}public static void main(String[] args) {Wind2 flute = new Wind2();Stringed2 violin = new Stringed2();Brass2 frenchHorn = new Brass2();tune(flute); // No upcastingtune(violin);tune(frenchHorn);}}

这样做当然行得通,但却存在一个极大的弊端:必须为每种新增的Instrument2 类编写与类紧密相关的方法。这意味着第一次就要求多得多的编程量。以后,假如想添加一个象tune()那样的新方法或者为Instrument 添加一个新类型,仍然需要进行大量编码工作。此外,即使忘记对自己的某个方法进行重载设置,编译器也不会提示任何错误。这样一来,类型的整个操作过程就显得极难管理,有失控的危险。但假如只写一个方法,将基础类作为自变量或参数使用,而不是使用那些特定的衍生类,岂不是会简单得多?也就是说,如果我们能不顾衍生类,只让自己的代码与基础类打交道,那么省下的工作量将是难以估计的。这正是“多形性”大显身手的地方。

3、进一步理解上塑造型

形状例子有一个基础类,名为Shape;另外还有大量衍生类型:Circle(圆形),Square(方形),Triangle(三角形)等等。大家之所以喜欢这个例子,因为很容易理解“圆属于形状的一种类型”等概念。
下面这幅继承图向我们展示了它们的关系:



上溯造型可用下面这个语句简单地表现出来:
Shape s = new Circle();
在这里,我们创建了Circle 对象,并将结果句柄立即赋给一个Shape。这表面看起来似乎属于错误操作(将一种类型分配给另一个),但实际是完全可行的——因为按照继承关系,Circle 属于Shape 的一种。因此编译器认可上述语句,不会向我们提示一条出错消息。
当我们调用其中一个基础类方法时(已在衍生类里覆盖):s.draw();
同样地,大家也许认为会调用Shape 的draw(),因为这毕竟是一个Shape 句柄。那么编译器怎样才能知道该做其他任何事情呢?但此时实际调用的是Circle.draw() ,因为后期绑定已经介入(多形性)。
"后期绑定":绑定在运行期间进行,以对象的类型为基础。
下面这个例子从一个稍微不同的角度说明了问题:
//Polymorphism in Javaclass Shape {void draw() {}void erase() {}}class Circle extends Shape {void draw() {System.out.println("Circle.draw()");}void erase() {System.out.println("Circle.erase()");}}class Square extends Shape {void draw() {System.out.println("Square.draw()");}void erase() {System.out.println("Square.erase()");}}class Triangle extends Shape {void draw() {System.out.println("Triangle.draw()");}void erase() {System.out.println("Triangle.erase()");}}public class Shapes {public static Shape randShape() {switch ((int) (Math.random() * 3)) {default: // To quiet the compilercase 0:return new Circle();case 1:return new Square();case 2:return new Triangle();}}public static void main(String[] args) {Shape[] s = new Shape[9];// Fill up the array with shapes:for (int i = 0; i < s.length; i++)s[i] = randShape();// Make polymorphic method calls:for (int i = 0; i < s.length; i++)s[i].draw();}}

运行结果:

在主类Shapes 里,包含了一个static 方法,名为randShape()。它的作用是在每次调用它时为某个随机选择的Shape 对象生成一个句柄。请注意上溯造型是在每个return 语句里发生的。这个语句取得指向一个Circle,Square 或者Triangle 的句柄,并将其作为返回类型Shape 发给方法。所以无论什么时候调用这个方法,就绝对没机会了解它的具体类型到底是什么,因为肯定会获得一个单纯的Shape 句柄。main()包含了Shape 句柄的一个数组,其中的数据通过对randShape()的调用填入。在这个时候,我们知道自己拥有Shape,但不知除此之外任何具体的情况(编译器同样不知)。然而,当我们在这个数组里步进,并为每个元素调用draw()的时候,与各类型有关的正确行为会魔术般地发生,产生了上面的运行结果。

4、扩展性

现在,让我们仍然返回乐器(Instrument)示例。由于存在多形性,所以可根据自己的需要向系统里加入任意多的新类型,同时毋需更改true()方法。在一个设计良好的OOP 程序中,我们的大多数或者所有方法都会遵从tune()的模型,而且只与基础类接口通信。我们说这样的程序具有“扩展性”,因为可以从通用的基础类继承新的数据类型,从而新添一些功能。如果是为了适应新类的要求,那么对基础类接口进行操纵的方法根本不需要改变。
对于乐器例子,假设我们在基础类里加入更多的方法,以及一系列新类,那么会出现什么情况呢?下面是示意图:


所有这些新类都能与老类——tune()默契地工作,毋需对tune()作任何调整。即使tune()位于一个独立的文件里,而将新方法添加到Instrument 的接口,tune()也能正确地工作,不需要重新编译。下面这个程序是对上述示意图的具体实现:
//An extensible programimport java.util.*;class Instrument3 {public void play() {System.out.println("Instrument3.play()");}public String what() {return "Instrument3";}public void adjust() {}}class Wind3 extends Instrument3 {public void play() {System.out.println("Wind3.play()");}public String what() {return "Wind3";}public void adjust() {}}class Percussion3 extends Instrument3 {public void play() {System.out.println("Percussion3.play()");}public String what() {return "Percussion3";}public void adjust() {}}class Stringed3 extends Instrument3 {public void play() {System.out.println("Stringed3.play()");}public String what() {return "Stringed3";}public void adjust() {}}class Brass3 extends Wind3 {public void play() {System.out.println("Brass3.play()");}public void adjust() {System.out.println("Brass3.adjust()");}}class Woodwind3 extends Wind3 {public void play() {System.out.println("Woodwind3.play()");}public String what() {return "Woodwind3";}}public class Music3 {// Doesn't care about type, so new types// added to the system still work right:static void tune(Instrument3 i) {// ...i.play();}static void tuneAll(Instrument3[] e) {for (int i = 0; i < e.length; i++)tune(e[i]);}public static void main(String[] args) {Instrument3[] orchestra = new Instrument3[5];int i = 0;// Upcasting during addition to the array:orchestra[i++] = new Wind3();orchestra[i++] = new Percussion3();orchestra[i++] = new Stringed3();orchestra[i++] = new Brass3();orchestra[i++] = new Woodwind3();tuneAll(orchestra);}}
在main()中,当我们将某样东西置入Instrument3 数组时,就会自动上溯造型到Instrument3。可以看到,在围绕tune()方法的其他所有代码都发生变化的同时,tune()方法却丝毫不受它们的影响,依然故我地正常工作。这正是利用多形性希望达到的目标。我们对代码进行修改后,不会对程序中不应受到影响的部分造成影响。此外,我们认为多形性是一种至关重要的技术,它允许程序员“将发生改变的东西同没有发生改变的东西区分开”。






0 0
原创粉丝点击