Thinking-in-Java 读书笔记-8-多态

原文地址

前言

记得刚接触Java的时候,整天被老师灌输的就是「封装」、「继承」、「抽象」和「多态」,因为这是面向对象语言基本的特征。尴尬的是,直到大学毕业了也没有彻底搞懂…

多态通过分离「做什么」和「怎么做」,从另一个角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能创建可扩展的程序–即无论在项目最初创建时还是需要添加新功能都可以生长的程序。

「封装」通过合并特征和行为来创建新的数据类型,实现隐藏则通过将细节私有化把接口和实现分离开来,这种类型的组织机制对那些拥有过程化程序设计背景的人来说,更容易理解。而「多态」的作用则是消除类型之间的耦合关系。

继承允许将对象视为它自己本身的类型或其他类型来加以处理。这种能力即为重要,因为它允许将多种类型(从同一基类导出的)视为同一类型处理。多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是从同一基类导出而来的。

定位具体子类

class Machine {
    void work() {
        System.out.println("Machine work!");
    }
}

class Computer extends Machine {
    @Override
    void work() {
        System.out.println("Computer work");
    }
}

class Light extends Machine {
    @Override
    void work() {
        System.out.println("Light work");
    }

    public static void main(String[] args) {
        run(new Computer());
        run(new Light());
    }

    static void run(Machine machine) {
        machine.work();
    }
}

run方法传入的是基类类型的Machine,这样就不需要创建适配各种类型的run方法,加强了可维护性和代码健壮性。利用了「向上转型」这一特性,不用在乎具体的实现,只需要传入接口即可。

machine.work();如编译器是怎么知道这个machine的引用是指向具体的实现的呢?「后期绑定」,它的含义就是运行时根据对象的类型进行绑定,后期绑定也叫做动态绑定或运行时绑定。编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。

Java 中除了staticfinal方法之外,其他所有的方法都是后期绑定,这意味着通常情况下,我们不必判定是否应该进行后期绑定。

缺陷

不能覆盖私有方法

域与静态方法

构造器的调用顺序

class Animal {
    Animal() {
        System.out.println("Animal");
    }
}

class Car {
    Car() {
        System.out.println("Car");
    }
}

class Water {
    Water() {
        System.out.println("Water");
    }
}

class People extends Animal{
    People() {
        System.out.println("People");
    }
}

class Student extends People {
    Student() {
        System.out.println("Student");
    }

    private Car car = new Car();
    private Water water = new Water();

    public static void main(String[] args) {
        People student = new Student();
    }
}

运行结果:

Animal
People
Car
Water
Student

顺序:

  1. 调用基类的构造器
  2. 按声明顺序调用成员的初始化方法
  3. 调用导出类构造器的主体

构造器内部的多态方法的行为

class Phone {

    Phone() {
        System.out.println("before call");
        call();
        System.out.println("after call");
    }

    void call() {
        System.out.println("Phone Call");
    }
}

class IPhone extends Phone {
    private String num = "110";
    IPhone(String num) {
        this.num = num;
        System.out.println("number: " + this.num);
    }

    @Override
    void call() {
        System.out.println("call number: " + num);
    }

    public static void main(String[] args) {
        IPhone iPhone = new IPhone("119");
    }
}

运行结果:

before call
call number: null
after call
number: 119

从结果可以看出,在执行基类的构造函数调用到多态方法时,会调用被覆盖的具体方法,但这个时候子类并没有完成初始化,导致获取到子类的成员是null

初始化的实际过程:

  1. 在其他任何事物发生之前,将分配个对象的存储空间初始化成二进制的零。
  2. 如前所述那样调用基类构造器
  3. 按照声明的顺序调用成员的初始化方法
  4. 调用导出类的构造器主体

向下转型

由于向上转型会丢失具体的类型信息,所以我们就像,通过向下转型应该能获取到类型数据。然而,我们知道向上转型是安全的,因为基类不会具有大余导出类的接口。因此,我们通过基类接口发送的消息保证都可以接受。但是对于向下转型,就无法知道是哪个具体的对象了。

因此需要进行强制转换,但是如果强转失败会抛出ClassCaseException的异常,因此在使用向下转型的时候需要进行类别的判断,进行容错处理。

总结

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

为了在自己的程序中有效地运用多态乃至面向对象的技术,必须扩展自己的编程视野,使其不仅包括个别类的成员和消息,而且要包括类与类之间的共同特性以及它们之间的关系。尽管这需要极大的努力,但是这样做是非创值得的,因为它可以带来很多成效:更快的程序开发过程、更好的代码组织、更好的扩展的程序以及更容易的代码维护等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值