Java为何需要多态机制?
先看一个程序代码,我们通过该程序多方面揣摩Java设计者设计多态机制的由来。
1 //:polymorphism/music/Note.java 2 3 package polymorphism.music; 4 5 public ennum Note{ 6 7 MIDDLE_C,C_SHARP,B_FLAT; 8 9 } 10 11 12 13 //:polymorphism/music/Instrument.java 14 15 package polymorphism.music; 16 17 Class Instrument{ 18 19 public void play(Note n){ 20 21 System.out.print("Instrument.play()"); 22 23 } 24 25 } 26 27 28 29 //:polymorphism/music/Wind.java 30 31 package polymorphism.music; 32 33 public class Wind extends Instrument{ 34 35 public void play(Note n){ 36 37 System.out.print("Wind.play()"+n); 38 39 } 40 41 } 42 43 44 45 //:polymorphism/music/ Music.java 46 47 package polymorphism.music; 48 49 public class Music{ 50 51 public static void tune(Instrument i){ 52 53 i.play(Note.MIDDLE_C); 54 55 } 56 57 58 59 public static void main(String args[]){ 60 61 Wind flute=new Wind(); 62 63 tune(flute); 64 65 } 66 67 } 68 69 70 71 /*输出结果: 72 73 Wind.play() MIDDLE_C 74 75 */
对于以上这个例子我们可以看出,由于Wind类从Instrument类继承而来,而在Java中支持向上转型(对象既可以作为它自己本身的类型使用,也可以作为它的基类型使用,这种在把对某个对象的引用视为对其基类的引用的做法称为向上转型——因为在继承树的画法中,基类是放置在上方的),理所当然当一个Wind引用传递到tune()方法时,不需要任何类型转换,这样Wind引用flute通过调用Wind的方法play(),输出结果:Wind.play() MIDDLE_C。
然而Music.java看起来似乎有些奇怪,为何所有人都故意忘记对象的类型?而要让其进行向上转型?这有违常规行为,如果让tune()方法直接接受一个Wind引用作为自己的参数,似乎会更为直观。这样的想法很直接,但这样会引发一个重要的问题:如果那样做就要为系统里的每一个Instrument类的子类都编写一个新的方法。假设按照这种推理,现在再加入Stringed和Brass这两种乐器,则代码如下:
1 //:polymorphism/music/Note.java 2 3 package polymorphism.music; 4 5 public ennum Note{ 6 7 MIDDLE_C,C_SHARP,B_FLAT; 8 9 } 10 11 12 13 //:polymorphism/music/Instrument.java 14 15 package polymorphism.music; 16 17 Class Instrument{ 18 19 public void play(Note n){ 20 21 System.out.println("Instrument.play()"); 22 23 } 24 25 } 26 27 28 29 //:polymorphism/music/Wind.java 30 31 package polymorphism.music; 32 33 public class Wind extends Instrument{ 34 35 public void play(Note n){ 36 37 System.out.println("Wind.play()"+n); 38 39 } 40 41 } 42 43 44 45 // package polymorphism.music; 46 47 public class Stringed extends Instrument{ 48 49 public void play(Note n){ 50 51 System.out.println("Stringed.play()"+n); 52 53 } 54 55 } 56 57 58 59 // package polymorphism.music; 60 61 public class Brass extends Instrument{ 62 63 public void play(Note n){ 64 65 System.out.println("Brass.play()"+n); 66 67 } 68 69 } 70 71 72 73 //:polymorphism/music/ Music.java 74 75 package polymorphism.music; 76 77 public class Music{ 78 79 public static void tune(Wind i){ 80 81 i.play(Note.MIDDLE_C); 82 83 } 84 85 public static void tune(Stringed i){ 86 87 i.play(Note.MIDDLE_C); 88 89 } 90 91 public static void tune(Brass i){ 92 93 i.play(Note.MIDDLE_C); 94 95 } 96 97 public static void main(String args[]){ 98 99 Wind flute=new Wind(); 100 101 Stringed violin=new Stringed(); 102 103 Brass frenchHorn =new Brass(); 104 105 tune(flute); 106 107 tune(violin); 108 109 tune(frenchHorn); 110 111 } 112 113 } 114 115 /*输出结果: 116 117 Wind.play() MIDDLE_C 118 119 Stringed.play() MIDDLE_C 120 121 Brass.play() MIDDLE_C 122 123 */
由上述例子的输出结果可以知道,这样做也行得通,但有一个缺点:必须为添加的每一个Instrument类的子类编写特定类型的tune()方法,这意味着需要编写更多的代码,而且如果以后每添加一个由Instrument导出的类就要在Music.java中重载一个tune()方法,这无疑提高了程序员的工作量。此外,如果我们忘记重载某个方法,编译器不会返回任何错误信息,这样关于类型的整个处理过程就显得难以操纵。
面对如此困境,我们急需一种更高明的方法去解决这一问题。如果运用多态,则很容易实现,相关代码如下:
1 //:polymorphism/music/Note.java 2 3 package polymorphism.music; 4 5 public ennum Note{ 6 7 MIDDLE_C,C_SHARP,B_FLAT; 8 9 } 10 11 12 13 //:polymorphism/music/Instrument.java 14 15 package polymorphism.music; 16 17 Class Instrument{ 18 19 public void play(Note n){ 20 21 System.out.println("Instrument.play()"); 22 23 } 24 25 } 26 27 28 29 //:polymorphism/music/Wind.java 30 31 package polymorphism.music; 32 33 public class Wind extends Instrument{ 34 35 public void play(Note n){ 36 37 System.out.println("Wind.play()"+n); 38 39 } 40 41 } 42 43 44 45 // package polymorphism.music; 46 47 public class Stringed extends Instrument{ 48 49 public void play(Note n){ 50 51 System.out.println("Stringed.play()"+n); 52 53 } 54 55 } 56 57 58 59 // package polymorphism.music; 60 61 public class Brass extends Instrument{ 62 63 public void play(Note n){ 64 65 System.out.println("Brass.play()"+n); 66 67 } 68 69 } 70 71 72 73 //:polymorphism/music/ Music.java 74 75 package polymorphism.music; 76 77 public class Music{ 78 79 public static void main(String args[]){ 80 81 Instrument flute=new Wind(); 82 83 Instrument violin=new Stringed(); 84 85 Instrument frenchHorn =new Brass(); 86 87 flute.play(Note. MIDDLE_C); 88 89 violin.play(Note. MIDDLE_C); 90 91 frenchHorn.play(Note. MIDDLE_C); 92 93 } 94 95 } 96 97 /*输出结果: 98 99 Wind.play() MIDDLE_C 100 101 Stringed.play() MIDDLE_C 102 103 Brass.play() MIDDLE_C 104 105 */
这样看起来代码是不是精简多了呢?这里就用到了Java的多态。在Java多态中,由于Java支持扩展类向上转型当成基类使用,不管导出类的存在,编写的代码只是与基类打交道。而这正是我们想要的结果。
这又让我们对多态的运作方式很是迷惑,究竟这种多态机制是如何实现的?凭什么允许我们这么做?在上例中编译器怎么知道Instrument引用指向的是哪一个扩展类的对象?
(待续)
以上所述若有不妥,欢迎拍砖!