『Thinking in Java 读书笔记』—— 8-多态

  |     |   本文总阅读量:

Thinking in java 读书笔记

前言

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

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

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

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

定位具体子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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方法之外,其他所有的方法都是后期绑定,这意味着通常情况下,我们不必判定是否应该进行后期绑定。

缺陷

不能覆盖私有方法

域与静态方法

构造器的调用顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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();
}
}

运行结果:

1
2
3
4
5
Animal
People
Car
Water
Student

顺序:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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");
}
}

运行结果:

1
2
3
4
before call
call number: null
after call
number: 119

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

初始化的实际过程:

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

向下转型

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

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

总结

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

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


赏我 e(=2.72) 元咖啡钱吧,您的支持将鼓励我继续创作!



文章目录
  1. 1. 前言
  2. 2. 定位具体子类
  3. 3. 缺陷
  4. 4. 构造器的调用顺序
  5. 5. 构造器内部的多态方法的行为
  6. 6. 向下转型
  7. 7. 总结
您是第 位小伙伴 | 本站总访问量 | 已经写了 85.9k 字啦

载入天数...载入时分秒...