JavaCore 1.多态和运行时类别识别

  • 面向对象三大特性之一的多态
    • 多态是什么
    • Java中的多态
    • 运行时类别识别

运行时类型识别(RTTI, Run-Time Type Identification)

  • 维护类的相关信息
  • 多态是基于RTTI实现的
  • RTTI主要功能由Class类实现

Class类

  • Class类是”类的类”(class of classes)

如果说类是对象的抽象和集合的话,那么Class类就是对类的抽象和集合

多态的原理

  • 每一个Class类的对象代表一个其他的类
  • 当调用对象的getClass() 方法时,就得到对应Class对象的引用
  • Java中每个对象都有相应的Class类对象,因此随时能通过Class对象知道某个对象”真正”所属的类
    • 无论对引用进行怎样的类型转换,对象本身所对应的Class对象都是同一个
    • 通过某个引用调用方法时,Java总能找到正确的Class类中所定义的方法,并执行该Class类中的代码
    • 由于Class对象的存在,Java不会因为类型的向上转换而迷失

Class类的加载

  • 当Java创建某个类的对象,比如Human类对象时,Java会检查内存中是否有相应的Class对象
    • 如果内存中没有相应的Class对象,那么Java会在.class文件中寻找Human类的定义,并加载Human类的Class对象
  • 在Class对象加载成功后,其他Human对象的创建和相关操作都将参照该Class对象

多态

概念

多态:一个对象变量可以指示多种实际类型的现象。

1
2
3
graph TD
A(多态)-->B(重载Overloading)
A(多态)-->C(重写Overriding)
三个必要条件
  1. 要有继承
  2. 要有重写
  3. 父类引用指向子类对象

Java语言中对象变量是多态的。如一个Employee变量既可以引用一个Employee类对象,也可以引用Employee类的任何一个子类的对象(Manager、Secretary、Programmer和Executive[单继承,通过Manager传递,Manager能引用Executive])。

不能将超类的引用赋给子类变量,不是所有的超类(human)都是子类(scientist)。

1
2
3
4
5
graph TD
A(Employee)-->B(Manager)
A-->C(Secretary)
A-->D(Programmer)
B-->E(Executive)

动态绑定

在运行时能够自动地选择调用那个方法的现象称为动态绑定(dynamic binding)

在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

方法储存在代码区(Code Seg),找到对应方法需要函数指针,而这个指针在new对象的时候被初始化,故可以迟邦定;而且方法的调用可以透过对象变量引用,看到变量的本质类型。

当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。假设x的实际类型是D(C的子类),如果D 类定义了该签名(方法的名字和参数列表)就直接调用,否则将在D类的超类中寻找,以此类推(超类的超类)。

动态绑定重要特性:易扩展性。

如,假设增加一个新类Executive,并且变量e有可能引用这个类的对象,我们不需要对包含调用e.f()的代码进行重新编译。如果e恰好引用一个Executive类的对象,就会自动地调用Executive.f()方法。

静态绑定

如果是方法、方法、方法或者构造器,那么编译器可以准确知道应该调用哪个方法,将这种调用方式成为静态绑定(static binding)

对象转型(casting)

  • 一个基类的引用类型变量可以“指向”其子类的对象。
  • 一个基类的引用不可以访问其子类对象新增加的成员(属性和方法)。
  • 可以使用用引用变量 instanceof类名来判断该引用型变量所“指向”的对象是否属于该类或该类的子类。
  • 子类的对象可以当作基类的对象来使用称作向上转型(upcasting),反之称为向下转型(downcasting)。
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.yza.myJavaBasic;

public class ObjectCast {

public static void main(String[] args) {
Animal a = new Animal("yin");
Dog d = new Dog("yin", "black");
Cat c = new Cat("yin", "black");

System.out.println(a instanceof Animal);
System.out.println(d instanceof Animal);
System.out.println(c instanceof Animal);
System.out.println(a instanceof Dog);

a = d;
System.out.println(a instanceof Animal);
System.out.println(a instanceof Dog);
System.out.println(a.getClass());
Dog d1 = (Dog) a;
System.out.println(d1.furColor);
// dynamic binding
Animal animal = new Animal("yin");
System.out.println(animal);
animal = d;
System.out.println(animal);
animal = c;
System.out.println(animal);
// casting的可扩展性
test(new Animal("yin"));
test(d);
test(c);
// 多态的可扩展性
Lady l1 = new Lady("huang", c);
Lady l2 = new Lady("xiong", d);
l1.petEnjoy();
l2.petEnjoy();
}

public static void test(Animal a) {
if(a instanceof Animal) {
System.out.println("test " + a);
} else if(a instanceof Dog) {
Dog dog = (Dog) a;
System.out.println("test " + dog + " " + dog.furColor);
} else if(a instanceof Cat) {
Cat cat = (Cat)a;
System.out.println("test " + cat + " " + cat.catColor);
}
}

}

class Animal {
String name;

public Animal(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}

public void enjoy() {
System.out.println("叫声。。。。。。");
}
}

class Dog extends Animal {
String furColor;

public Dog(String name, String color) {
super(name);
this.furColor = color;
}
@Override
public String toString() {
return "Dog " + super.toString() + " " + furColor;
}
@Override
public void enjoy() {
System.out.println("狗叫。。。。。。");
}
}

class Cat extends Animal {
String catColor;

public Cat(String name, String catColor) {
super(name);
this.catColor = catColor;
}
@Override
public String toString() {
return "Cat " + super.toString() + " " + catColor;
}
@Override
public void enjoy() {
System.out.println("猫叫。。。。。。");
}
}

class Lady {
String name;
Animal pet;

public Lady(String name, Animal animal) {
this.name = name;
this.pet = animal;
}

public void petEnjoy() {
pet.enjoy();
}
}

几种说法

  1. Java语言中对象变量是多态的。如一个Employee变量既可以引用一个Employee类对象,也可以引用Employee类的任何一个子类的对象(Manager、Secretary、Programmer和Executive[单继承,通过Manager传递,Manager能引用Executive])

    不能将超类的引用赋给子类变量,不是所有的超类(human)都是子类(scientist)。
  2. 将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行类型转换(1.只能在继承层次内进行类型转换;2.在将超类转换成子类之前,应该使用instanceOf进行检查)
    1
    2
    3
    4
    if(a instanceof ClassB) {
    objectB = (ClassB)a;
    ...
    }