JAVA编程思想笔记 第八章 多态

Outlines

  • 多态意味着不同的形式
  • 我们持有从基类继承而来的相同的接口以及使用该接口的不同的形式
  • 不同版本的动态绑定方法

8.1. 基本概念

  • 多态是继数据抽象和集成指挥的第三种基本类型特征
  • 多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开,具有可扩展性
    • 封装: 通过合并特征和行为来创建新的数据类型;
    • 实现隐藏: 通过细节私有化把接口和实现分离开来
    • 多态: 消除类型间的耦合关系
    • 堕胎允许调用一种类型表现出与其他相似类型之间的区别
    • 动态绑定, 后期绑定, 运行时绑定

8.2. 向上转型

  • 向上转型会缩小接口, 但是不会比基类的全部接口更窄
class Instrument { 
  public void play(Note n) {}
}
public class Wind extends Instrument {
  public void play(Note n) {}
}
public class Music {
  public static void tune(Instrument i) { i.play(Note.MIDDLE_C);}
  public static void main(String[] args) {
    Wind flute = new Wind();
    tune(flute); // Upcasting
  }
}
  • 如果直观的为每一个子类都生成一个单独的方法, 每增加一个子类都需要编写特定的方法

绑定

  • 将一个方法调用同一个方法主体关联起来的行为叫做绑定, 前期绑定再编译期实现
  • 后期绑定也叫做动态绑定/运行时绑定, 需要再运行时就能判断对象的类型
  • Java中除了static和final方法, 其他的方法都是后期绑定
class RandomShapeGenerator {
  private Random rand = new Random(47);
  public Shape next() {
    switch (rand.nextInt(3)) {
      default:
      case 0: return new Circle();
      case 1: return new Square();
      case 2: return new Triangle();
    }
  }
}
...
RandomShapeGenerator gen = new RandomShapeGenerator();
Shape[] s = new Shape[9];
for (int i = 0; i < s.length; i++) s[i] = gen.next();
for (Shape shp : s) shp.draw(); // Make polymorphic method calls:

实际输出是按照后期绑定的方法输出,而不是基类shape的方法.

可扩展属性

  • 方法可以完全忽略周围代码所发生的全部变化,依旧正常运行
  • 多态是一项让改变的事物和未改变的事务分离开来的技术

缺陷: 覆盖私有方法

  • 私有方法在子类中不可见, 也不可覆盖

缺陷: 域和静态方法

  • 域和静态方法不具有多态性, 调用时需要使用super.field进行

8.3. 构造器和多态

  • 构造器是static方法, 因此不具有多态性
  • 构造器的调用顺序
    1. 调用基类构造器并不断递归下去, 基类的构造器总是在导出类的构造过程中被调用, 而且按照继承层次逐渐向上连接
    2. 按声明顺序调用成员的初始化方法
    3. 调用导出类构造器的主题
  • 清理
    • 销毁的顺序应该与初始化的顺序相反, 对于字段, 则意味着和生命顺序相反
    • 首先清理导出类然后再是基类, 因为销毁过程中可能还会调用基类方法
  • 构造器内部的多态方法的行为
    • 在一个构造器内部调用正在构造的对象的某个动态绑定方法
    • 一个动态绑定的方法调用却向外深入到继承层次结构内部,它可以调用导出类里面的方法
    class Glyph {
      Glyph() {
        System.out.println("Glyph() before draw()");
        draw();
        System.out.println("Glyph() after draw()");
      }
      void draw() {
        System.out.println("Glyph.draw()");
      }
    }
    class RoundGlyph extends Glyph {
      private int radius = 1;
      RoundGlyph(int r) {
        radius = r;
        System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
      }    
      void draw() {
        System.out.println("RoundGlyph.draw(), radius = " + radius);
      }
    }    
    public class PolyConstructors {
      public static void main(String[] args) {
        new RoundGlyph(5);
      }
    }
    /* output:
    Glyph() before draw()
    RoundGlyph.draw(), radius = 0
    Glyph() after draw()
    RoundGlyph.RoundGlyph(), radius = 5
    *///
  • 初始化顺序
    1. 在其他任何事物发生之前, 将分配给对象的存储空间初始化成二进制的零
    2. 如前所述的调用基类构造器, 此时调用被覆盖后的draw()方法, 由于步骤一的原因, radius = 0
    3. 按照声明的顺序调用成员的初始化方法
    4. 调用子类的构造器主体
    5. 构造器中唯一安全调用的方法是基类中final方法.
  • 协变返回类型
    • 表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种到处类型
    • 子类中的成员函数的返回值类型不必严格等同于父类中被重写的成员函数的返回值类型,而可以是更 "狭窄" 的类型。
class Grain {public String toString() { return "Grain";}}
class Wheat extends Grain { public String toString() {return "Wheat";}}
class Mill { Grain process() {return new Grain();}}
class WheatMill extends Mill { Wheat process() {return new Wheat();}}

public class CovariantReturn {
  public static void main(String[] args) {
    Mill m = new Mill();
    Grain g = m.process();
    System.out.println(g);
    m = new WheatMill();
    g = m.process();
    System.out.println(g);
  }
}
//Grain
//Wheat

向下转型

  • 向上转型会丢失信息,向下转型应该能够获取类型的信息
  • 必须有方法来保证向下转型的正确性
  • RTTI

猜你喜欢

转载自www.cnblogs.com/fireyjy/p/12527176.html