Java面向对象_04_多态


1. 多态

面向对象三特特征中,除了封装、继承,还有多态。(为什么出现这种需求,不是很理解

关于多态中涉及到的几个概念:

  • 向上转型(upcasting):

    子类型转换成父类型,也被称为自动类型转换。

  • 向下转型(downcasting):

    父类型转换成子类型,也被称为强制类型转换。(需要加强制类型转换)

  • 注意,无论是向上转型还是向下转型,两种类型之间必须有继承关系。没有继承关系,程序是无法编译通过的。

多态即多种状态,在Java程序中,有编译和运行两个阶段。多态指的是编译时是一种状态,运行时是另一种状态。比如向上转型之后(左侧引用是父类型,右侧实际对象是子类型),父类型引用指向子类型对象。即在编译的时候,父类型引用调用自己类的方法;但是在运行的时候,调用的其实是子类中的同名方法。即多种状态。

多态可以降低程序的耦合度,提高程序的可扩展力。能使用多态尽量使用多态,核心是面向抽象编程,尽量不要面向具体编程。多态的底层实质上还是指向的最终的那个具体的类对象。

这样有一个好处,就是在传参的时候,形参可以只定义一种(父类型引用),实参可以传入它的任何子类。类似重载而又不需要定义多个方法。

1.1 流程分析

Animal、Cat、Bird三个类之间的关系:

  • Cat继承Animal
  • Bird继承Animal
  • Cat和Bird之间没有任何关系
1
2
Animal a = new Cat();
a.move();

分析:

首先Animal和Cat之间存在继承关系,并且Animal是父类,Cat是子类。Animal类中有move()方法,Cat is a Animal,new Cat() 创建的对象的类型是Cat,a这个引用的数据类型是Animal,可见它们进行了类型转换。子类型转换成父类型,称为向上转型。(注意,这是把Cat对象的内存地址赋值给Animal类型的引用,所以是子类型转换成父类型),是自动类型转换。

  1. Java程序分为编译阶段和运行阶段,先编译再运行。
  2. 在编译阶段,编译器检查语法,a的引用数据类型为Animal,父类型指向子类型,满足自动类型转换,并且Animal.class字节码文件中有move方法,所以编译通过了。这个编译阶段绑定的过程称为静态绑定,只有静态绑定成功之后才有后续的运行。
  3. 在运行阶段,JVM堆内存当中真实创建的对象是Cat对象,那么运行阶段则会调用Cat对象的move()方法,此时发生了程序的动态绑定,在运行阶段绑定。
  4. 注意,无论Cat有没有重写move()方法,运行阶段一定调用的是Cat对象的move方法,因为底层的真实对象是Cat对象。
  5. 父类型引用指向子类型对象这种机制导致程序在编译阶段和运行阶段绑定两种不同的状态。

这种机制可以称为一种多态机制,本科时候,似乎可以这么认为,这种向上转型就是:父类型的引用仅仅能访问 子类型对象中 继承父类中的那部分区域。

1
2
3
4
5
6
Animal a = new Cat();

a.catchMouse(); // 报错,编译阶段不通过

Cat c = (Cat)a;
c.catchMouse();

分析:

上述的注释行就会报错,因为编译阶段,检测到a是Animal数据类型,但是.class字节码文件中的Animal并没有catchMouse()方法,所以静态绑定失败,即编译未通过。

为了执行catchMouse()方法,必须使得编译通过,即Cat类型才有catchMouse()方法,此时可以向下转型,即子类型引用指向父类型对象。将a的数据类型转换为Cat,即第三行的强制类型转换。

注意,向下转型也需要两种类型之间有继承关系,不然编译报错,强制类型转换需要加强制类型转换符。

和向上转型对比,什么时候需要使用向下转型呢?

前面提到过,向上转型只能访问父类型中有的,那么当调用的方法或者访问的属性是子类型中特有的,就必须进行向下转型。

注意,此时可能会有疑问,向上转型只能访问到父类型中有的。这肯定是不满足要求的,否则继承的意义就不是那么明显了。为了访问子类型特有的,还需要向下转型。转来转去还是回到了原来,这不累吗?

其实不是的,父类型引用指向子类型对象,这部分满足了形参可以只定义一种类型,而实参则可以传多种子类型,类似重载。传进来之后,可根据需求进行向下类型转换,降低耦合度,提高可扩展力。假如有多个子类,如何判断向下转换到哪个子类型呢,参加案例中的instanceof关键字。instanceof是用来判断前者是否是后者的一个实例,(也就是在底层内存上,前者是否指向的是后者)。

2. 简单案例

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
class Animals{
public void move(){
System.out.println("动物在移动!");
}
}

class Cat extends Animals{

// 重写父类move方法
public void move(){
System.out.println("猫在走猫步!");
}

// Cat类独有的方法
public void catchMouse(){
System.out.println("猫在抓老鼠!");
}
}

class Bird extends Animals{

// 重写父类move方法
public void move(){
System.out.println("鸟儿在飞翔!");
}

// Bird类独有的方法
public void fly(){
System.out.println("鸟儿飞呀飞!");
}
}

public class TestOOP_03 {

public static void castClass(Animals animals){
animals.move();

// 按条件判断向下转型的子类型,然后向下转,再调用子类型特有的方法。
if(animals instanceof Cat){
((Cat) animals).catchMouse();
}

if(animals instanceof Bird){
((Bird) animals).fly();
}
}

public static void main(String[] args){
// 没有多态时的写法
// 即创建对应的对象,然后调用对应的方法
System.out.println("------多态前------");
Animals aml = new Animals();
aml.move();

Cat cat = new Cat();
cat.move();

Bird bird = new Bird();
bird.move();

// 有了多态后,可以向上转型(自动转)
System.out.println("------多态后(普通,向上转)------");
Animals aml_1 = new Animals();
aml_1.move();

Animals aml_2 = new Cat(); // 父类型Animals引用指向 子类型Cat对象,自动类型转换,
aml_2.move(); // 表面上编译时调用的是Animals类对对象的move方法,但运行的时候,调用的是Cat类对象的方法
// aml_2.catchMouse(); // 编译失败,Animals类型没有catchMouse()方法。可采用下面向上转型

Cat c = (Cat)aml_2; // 强转
c.catchMouse();

Animals aml_3 = new Bird();
aml_3.move();

System.out.println("-----------------------------------");
castClass(aml_2);

System.out.println("-----------------------------------");
castClass(aml_3);

}
}

3. 备注

参考B站《动力节点》。


文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录