《java编程思想——第八章(多态)》

多态

多态是继数据抽象和继承后的第三种基本特征。

8.1 再论向上转型

/**
 * 基类
 */
public class Instrument {

    public void play(Note n) {
        System.out.println("Instrument play()");
    }

}
/**
 * 导出类
 */
public class Wind extends Instrument{
    public void play(Note n) {
        System.out.println("Wind play()"+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);
    }
}

向上转型屏蔽了对象的类型,避免了为每个对象都重载一次tune方法。

多态允许定义方法时,仅接受基类作为参数,对于导出类同样可以正常使用。

8.2 转机

编译器是如何知道tune方法指向的是哪个方法呢?实际上编译器无法得知。
将一个方法调用同方法主题关联起来被称作绑定
1. 方法调用绑定
前期绑定:在程序执行前绑定。
后期绑定:在运行时根据对象的类型进行绑定。

Java中除了static方法和final方法,其他方法都是后期绑定。通过动态绑定实现多态。
只有非private方法才可以被覆盖;只有普通方法调用可以是多态的,静态方法与类相关联,而非单个对象。

8.3 构造器与多态

  1. 构造器调用顺序
    《java编程思想——第八章(多态)》_第1张图片
    在导出类可以访问基类public和protected的成员,这意味着在导出类中必须保证基类的所有成员都是有效的。所以要首先调用基类构造器,保证基类的成员有效初始化。
  2. 构造器内部的多态方法的行为

根据构造器调用的层次结构引出问题:如果在一个构造器的内部调用正在构造对象的某个动态绑定方法会发生什么情况?

如果要调用构造器内部的一个动态绑定方法,子类就要对这个方法进行覆盖。

/**
 * 构造器内部动态调用
 */
public class PolyConstructors {
    public static void main(String[] args) {
        new RoundGlyph(5);
    }
}
class Glyph{
    void draw(){
        System.out.println("Glyph.draw()");
    }
    public Glyph() {
        System.out.println("Glyph before draw()");
        draw();
        System.out.println("Glyph after draw()");
    }
}
class RoundGlyph extends Glyph{
    private int radius = 1;

    public RoundGlyph(int r) {
        radius = r;
        System.out.println("RoundGlyph.RoundGlyph(),radius = "+radius);
    }
    void draw(){
        System.out.println("RoundGlyph.draw,radius = "+radius);
    }
}

输出结果:

Glyph before draw()
RoundGlyph.draw,radius = 0 //实现了动态调用,但radius 初始值并不为1
Glyph after draw()
RoundGlyph.RoundGlyph(),radius = 5

构造器的初始化实际过程:
1)在其他任何事发生之前,将分配给对象的存储空间初始化成二进制零。
2)调用基类构造器(调用被覆盖的draw方法),由于1的原因,radius 的值为零
3)按照声明的顺序调用成员的初始化方法
4)调用导出类的构造器主体。

所以在构造器中唯一能安全调用是是final修饰的方法,应当避免调用动态方法。

8.4协变返回类型

协变返回类型:导出类中的被覆盖的方法可以返回基类方法的返回类型的某种导出类型。

/**
 * 协变返回类型
 * @author Administrator
 *
 */
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);
    }
}
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();
    }
}

8.5 用继承继续设计

相比继承,组合模式则更加灵活,可以在运行时改变对象状态。

/**
 * 状态模式
 * @author Administrator
 *
 */
public class Transmogrify {
    public static void main(String[] args) {
        Stage stage = new Stage();
        stage.performPlay();
        stage.change();
        stage.performPlay();
    }
}
class Actor{
    public void act() {}
}
class HappyActor extends Actor{
    public void act() {
        System.out.println("HappyActor");
    }
}
class SadActor extends Actor{
    public void act() {
        System.out.println("SadActor");
    }
}
class Stage{
    private Actor actor = new HappyActor();
    public void change() {
        actor = new SadActor();
    }
    public void performPlay() {
        actor.act();
    }
}

向下转型过程中需要进行类型转换,如果不存在则会抛出异常,这种在运行期间对类型检查的行为称作运行时类型检查

总结:
这里写图片描述

你可能感兴趣的:(java核心思想)