读Java编程思想随笔の多态
在面向对象程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。
多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能够创建可扩展的程序---即无论在项目最初创建时还是在需要添加新功能时都可以“生长”的程序。
“封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。这种类型的组织机制对于那些拥有过过程式程序开发背景的人来说,更容易理解。而多态的作用则是消除类型之间的耦合关系。我们知道,继承允许将对象视为它自己本身的类型或基类型来加以处理。这种能力极为重要,因为它允许将多种类型视为同一类型来处理,而另一份代码也就可以毫无差别的运行在这些不同类型之上。多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是从同一基类导出来的。这种区别是根据方法行为的不同而表示出来,虽然这些方法都可以通过同一个基类来调用。
向上转型
对象既可以作为它自己本身的类型使用,也可以作为它的基类型使用,而这种把对某个对象的引用视为对其基类型的引用的做法称为向上转型。
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 音符枚举 6 */ 7 public enum Note { 8 MIDDLE_C,C_SHARP,B_FLAT; 9 }
/** * @author yxm * @date 2015/11/1. * @company 中国奇奇科技有限公司 * @description 向上转型 */ public class Wind extends Instrument{ public void play (Note n) { System.out.println("Wind play() "+n); } } class Instrument { public void play (Note n) { System.out.println("Instrument.play ()"); } }
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 向上转型 6 */ 7 public class Music { 8 public static void tune (Instrument i) { 9 i.play(Note.MIDDLE_C); 10 } 11 12 public static void main(String[] args) { 13 Wind flute = new Wind (); 14 tune (flute); 15 } 16 } 17 //Wind play() MIDDLE_C
方法调用绑定
将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定。读者可能以前从没有听说过这个术语,因为它是面向过程的语言中不需要选择就默认的绑定方式。
而对于后期绑定,它的含义就是在运行时根据对象的类型进行绑定。后期绑定又叫动态绑定或运行时绑定。如果一种语言想实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而调用恰当地方法。也就说,编译器一直不知道对象的类型,但是方法调用机制能找到对应的方法体,并加以调用。后期绑定机制随编程语言的不同而有所不同,但是只要想一下就会得知,不管怎么样都必须在对象中安置某种“类型信息”。
Java中除了static方法和final方法外(private方法属于final方法),其他所有的方法都是后期绑定。这意味着通常情况下,我们不必判定是否应该后期绑定,它会自动发生。
为什么要将某个方法声明为final呢?它可以防止其他人覆盖该方法,但更重要一点或许是:这样做可以有效的关闭动态绑定。或者说,告诉编译器不需要对此方法进行动态绑定。这样,编译器就可以为final方法调用生成更有效的代码。然而,大多数情况下,这样做,对程序的整体性能不会有多少改观。所以,最好根据设计来决定是否使用final,而不是出于试图提高性能的目的使用final。
一旦知道Java中所有方法都是通过动态绑定实现多态这个事实之后,我们就可以编写只与基类打交道的程序代码了,并且这些代码对所有导出类都可以正确的运行。或者换一种说法,发送消息给某个对象,让该对象去判定应该做什么事。
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 后期绑定 (多态) 6 */ 7 public class Shape { 8 public void draw () {}; 9 public void erase () {}; 10 }
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 后期绑定 (多态) 6 */ 7 public class Circle extends Shape { 8 @Override 9 public void draw() { 10 //super.draw(); 11 System.out.println("Circle.draw ()"); 12 } 13 14 @Override 15 public void erase() { 16 //super.erase(); 17 System.out.println("Circle.erase ()"); 18 } 19 }
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 后期绑定 (多态) 6 */ 7 public class Square extends Shape { 8 @Override 9 public void draw() { 10 //super.draw(); 11 System.out.println("Square.draw()"); 12 } 13 14 @Override 15 public void erase() { 16 //super.erase(); 17 System.out.println("Square.erase()"); 18 } 19 }
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 后期绑定 (多态) 6 */ 7 public class Triangle extends Shape{ 8 @Override 9 public void draw() { 10 //super.draw(); 11 System.out.println("Triangle.draw()"); 12 } 13 14 @Override 15 public void erase() { 16 //super.erase(); 17 System.out.println("Triangle.erase()"); 18 } 19 }
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 后期绑定 (多态) 6 */ 7 public class RandomShapeGenerator { 8 private Random rand = new Random(47); 9 public Shape next(){ 10 switch (rand.nextInt(3)){ 11 default: 12 case 0:return new Circle(); 13 case 1:return new Square(); 14 case 2:return new Triangle(); 15 } 16 17 } 18 19 public static void main(String[] args) { 20 Random r1 = new Random(47); 21 Random rand1 = new Random(47); 22 // Random r2 = new Random(); 23 // Random rand2 = new Random(); 24 // Random r3 = new Random(56); 25 // Random rand3 = new Random(65); 26 //47表示种子数,如果种子数相同,则代表引用r和引用rand指向同一片存储空间 27 //如果new Random()种子数不填或者填写的种子数不同,则代表引用r和引用rand指向不同的存储空间 28 System.out.println(r1.nextInt(66)); 29 System.out.println(rand1.nextInt(66)); 30 } 31 }
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 后期绑定 (多态) 6 */ 7 public class Shape { 8 public void draw () {}; 9 public void erase () {}; 10 }
私有方法被“覆盖”的情况
/** * @author yxm * @date 2015/11/1. * @company 中国奇奇科技有限公司 * @description 覆盖私有方法 */ public class PrivateOverride { private void f(){ System.out.println("private f()"); } public static void main(String[] args) { PrivateOverride pri = new Derived(); pri.f(); } } class Derived extends PrivateOverride { public void f(){ System.out.println("public f()"); } }
//private f()
只有非private方法才可以被覆盖,但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行。确切的说,在导出类中,对于基类中的private方法,最好采用不同的名称。
域与静态方法的多态
域由编译器解析,而方法则是代码执行中绑定
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 域不具备多态性 6 */ 7 public class FieldAccess { 8 public static void main(String[] args) { 9 Super sup = new Sub(); 10 System.out.println("sup.field="+sup.field+", sup.getField()="+sup.getField()); 11 Sub sub = new Sub(); 12 System.out.println("sub.field="+sub.field+", sub.getField()="+sub.getField()+", sub.getSuperField()="+sub.getSuperField()); 13 } 14 } 15 class Super{ 16 public int field = 0; 17 public int getField(){ 18 return field; 19 } 20 } 21 class Sub extends Super{ 22 public int field = 1; 23 public int getField(){ 24 return field; 25 } 26 public int getSuperField(){ 27 return super.field; 28 } 29 } 30 //sup.field=0, sup.getField()=1 31 //sub.field=1, sub.getField()=1, sub.getSuperField()=0 32 //域不具有多态,只有方法具有多态性
1 /** 2 * @author yxm 3 * @date 2015/11/1. 4 * @company 中国奇奇科技有限公司 5 * @description 静态方法不具备多态性 6 */ 7 public class StaticPolymorphism { 8 public static void main(String[] args) { 9 StaticSuper sup = new StaticSub(); 10 System.out.println(sup.dynamicGet()); 11 System.out.println(sup.staticGet()); 12 } 13 } 14 class StaticSuper{ 15 public static String staticGet(){ 16 return "Base staticGet()"; 17 } 18 public String dynamicGet(){ 19 return "Base dynamicGet()"; 20 } 21 } 22 class StaticSub extends StaticSuper{ 23 public static String staticGet(){ 24 return "Derived staticGet()"; 25 } 26 public String dynamicGet(){ 27 return "Derived dynamicGet()"; 28 } 29 } 30 //Derived dynamicGet() 31 //Base staticGet() 32 //静态方法不具有多态性,静态方法不与对象关联,而与类关联
构造器与多态
构造器不同于其他种类的方法。涉及到多态时,仍是如此。尽管构造器并不具有多态性(它们实际上是static方法,只不过该static声明是隐式的),但还是非常有必要理解构造器怎样通过多态在复杂的层次结构中运作。
构造器的调用顺序
基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能够得到调用。这样做是有意义的,因为构造器具有一项特殊任务,检查对象是否被正确的构造。导出类只能访问它自己的成员,不能访问基类中的成员。只有基类的构造器才具有恰当地知识和权限来对自己的元素进行初始化。因此,必须令所有的构造器都得到调用,否则就不可能正确的构造完整对象。这正是编译器为什么要强制每个导出类部分都必须调用构造器的原因。在导出类的构造器主体中,如果没有明确指定调用某个基类构造器,它就会默默的调用默认构造器。如果不存在默认构造器,编译器就会报错。
1 public class Sandwich extends PortableLunch{ 2 Sandwich(){ 3 System.out.println("Sandwich()"); 4 } 5 private Bread b = new Bread(); 6 private Cheese c = new Cheese(); 7 private Lettuce l = new Lettuce(); 8 9 public static void main(String[] args) { 10 new Sandwich(); 11 } 12 } 13 class Meal{ 14 Meal(){ 15 System.out.println("Meal()"); 16 } 17 } 18 class Bread{ 19 Bread(){ 20 System.out.println("Bread()"); 21 } 22 } 23 class Cheese{ 24 Cheese(){ 25 System.out.println("Cheese()"); 26 } 27 } 28 class Lettuce{ 29 Lettuce(){ 30 System.out.println("Lettuce()"); 31 } 32 } 33 class Lunch extends Meal{ 34 Lunch(){ 35 System.out.println("Lunch()"); 36 } 37 } 38 class PortableLunch extends Lunch{ 39 PortableLunch(){ 40 System.out.println("PortableLunch()"); 41 } 42 } 43 //Meal() 44 //Lunch() 45 //PortableLunch() 46 //Bread() 47 //Cheese() 48 //Lettuce() 49 //Sandwich
在此例中,对象调用构造器要遵照下面的顺序:
1、调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最底层导出类;
2、按声明顺序调用成员的初始化方法
3、调用导出类构造器的主体
构造器的调用顺序是很重要的。当进行继承时,我们一切知道基类的一切,并且可以访问基类中声明为public和protected的成员。这意味这在导出类中,必须假定基类的所有成员都是有效的。一种标准方法是,构造动作一经发生,那么对象所有部分的全体成员都会得到构建。然而,在构造器内部,我们必须确保所要使用的成员都已构建完毕。为确保这一目的,唯一的办法就是首先调用基类构造器。那么在进入导出类构造器时,在基类中可供我们访问的成员都已得到了初始化。此外,知道构造器中所有成员都有效也是因为当成员对象在类中进行定义时,只要有可能,就应该对它们进行初始化。若遵循这一规则,那么就能保证所有基类成员以及当前对象的成员对象都已被初始化。
继承和清理
通过组合方法和继承来创建新类时,永远不必担心对象的清理问题,子对象通常都会留给垃圾回收器进行处理。如果确实遇到清理的问题,那么必须用心为新类创建dispose()方法。并且由于继承的缘故,如果我们有其他作为垃圾回收器一部分的特殊清理动作,就必须在导出类中覆盖dispose()方法。当覆盖被继承类的dispose()方法时,务必记住调用基类版本dispose()方法;否则,基类的清理动作就不会发生。
1 ublic class Frog extends Amphibian{ 2 private Characteristic c = new Characteristic("Croaks"); 3 private Description d = new Description("Eats Bugs"); 4 Frog(){ 5 System.out.println("Frog()"); 6 } 7 protected void dispose(){ 8 System.out.println("Frog dispose"); 9 c.dispose(); 10 d.dispose(); 11 super.dispose(); 12 } 13 14 public static void main(String[] args) { 15 Frog frog = new Frog(); 16 System.out.println("bye bye"); 17 frog.dispose(); 18 } 19 } 20 class Characteristic{ 21 private String s; 22 Characteristic(String s){ 23 this.s = s; 24 System.out.println("Creating Characteristic "+s); 25 } 26 protected void dispose(){ 27 System.out.println("disposing Characteristic "+s); 28 } 29 } 30 class Description{ 31 private String s; 32 Description(String s){ 33 this.s = s; 34 System.out.println("Creating Description "+s); 35 } 36 protected void dispose(){ 37 System.out.println("disposing Description "+s); 38 } 39 } 40 class LivingCreature{ 41 private Characteristic p = new Characteristic("is alive"); 42 private Description d = new Description("Basic Living Creature"); 43 LivingCreature(){ 44 System.out.println("LivingCreature()"); 45 } 46 protected void dispose(){ 47 System.out.println("LivingCreature dispose"); 48 d.dispose(); 49 p.dispose(); 50 } 51 } 52 class Animal extends LivingCreature{ 53 private Characteristic c = new Characteristic("has heart"); 54 private Description d = new Description("Animal not Vegetable"); 55 Animal(){ 56 System.out.println("Animal()"); 57 } 58 protected void dispose(){ 59 System.out.println("Animal dispose"); 60 c.dispose(); 61 d.dispose(); 62 super.dispose(); 63 } 64 65 } 66 class Amphibian extends Animal{ 67 private Characteristic c = new Characteristic("can live in water"); 68 private Description d = new Description("Both water and land"); 69 Amphibian(){ 70 System.out.println("Amphibian()"); 71 } 72 protected void dispose(){ 73 System.out.println("Amphibian dispose()"); 74 c.dispose(); 75 d.dispose(); 76 super.dispose(); 77 } 78 } 79 //Creating Characteristic is alive 80 //Creating Description Basic Living Creature 81 //LivingCreature() 82 //Creating Characteristic has heart 83 //Creating Description Animal not Vegetable 84 //Animal() 85 //Creating Characteristic can live in water 86 //Creating Description Both water and land 87 //Amphibian() 88 //Creating Characteristic Croaks 89 //Creating Description Eats Bugs 90 //Frog() 91 //bye bye 92 //Frog dispose 93 //disposing Characteristic Croaks 94 //disposing Description Eats Bugs 95 //Amphibian dispose() 96 //disposing Characteristic can live in water 97 //disposing Description Both water and land 98 //Animal dispose 99 //disposing Characteristic has heart 100 //disposing Description Animal not Vegetable 101 //LivingCreature dispose 102 //disposing Characteristic is alive 103 //disposing Description Basic Living Creature
层次结构中的每个类都包含Characteristic和Description这两种类型的成员对象,并且它们也必须被销毁。所以万一某个子对象要依赖于其他对象,销毁的顺序应该和初始化顺序相反。对于字段,则意味着和声明的顺序相反(因为字段的初始化是按照声明的顺序进行的)。对于基类,应该首先对导出类进行清理,然后才是基类。这是因为导出类的清理可能会调用基类中的某些方法,所以需要使基类中的构建仍起作用而不应过早的销毁它们。
构造器内部的多态的方法行为
构造器内部调用的普通方法不会是基类的方法,即使在使用多态的情况下:
1 public class PolyConstructors { 2 public static void main(String[] args) { 3 new RoundGlyph(5); 4 } 5 } 6 class Glyph{ 7 void draw(){ 8 System.out.println("Glyph.draw()"); 9 } 10 Glyph(){ 11 System.out.println("Glyph() before draw()"); 12 draw(); 13 System.out.println("Glyph() after draw()"); 14 } 15 } 16 class RoundGlyph extends Glyph{ 17 private int radius = 1; 18 RoundGlyph(int i){ 19 this.radius = i; 20 System.out.println("RoundGlyph.RoundGlyph(),radius="+radius); 21 } 22 void draw(){ 23 System.out.println("RoundGlyph.draw(),radius="+radius); 24 } 25 } 26 //Glyph() before draw() 27 //Glyph.draw() 28 //Glyph() after draw() 29 //RoundGlyph.RoundGlyph(),radius=5
协作返回类型
1 public class CovariantReturn { 2 public static void main(String[] args) { 3 Mill m = new Mill(); 4 Grain g = m.process(); 5 System.out.println(g); 6 m = new WheatMill(); 7 g = m.process(); 8 System.out.println(g); 9 } 10 } 11 class Grain{ 12 @Override 13 public String toString() { 14 return "Grain"; 15 } 16 } 17 class Wheat extends Grain{ 18 @Override 19 public String toString() { 20 return "Wheat"; 21 } 22 } 23 class Mill{ 24 Grain process(){ 25 return new Grain(); 26 } 27 } 28 class WheatMill extends Mill{ 29 @Override 30 Wheat process(){ 31 return new Wheat(); 32 } 33 } 34 //Grain 35 //Wheat