Skip to main content

继承和多态

视频

从类型体系的角度理解继承和多态

  • 继承是一个类的子类,自动具备了父类的属性和方法,除非被父类声明为私有的
  • 多态是同一个类的不同子类,在调用同一个方法时会执行不同的动作
// 面向对象继承和多态示例
class Mammal{
int weight = 20;
boolean canSpeak(){
return true;
}

void speak(){
println("mammal speaking...");
}
}

class Cow extends Mammal{
void speak(){
println("moo~~ moo~~");
}
}

class Sheep extends Mammal{
void speak(){
println("mee~~ mee~~");
println("My weight is: " + weight); //weight的作用域覆盖子类
}
}

//将子类的实例赋给父类的变量
Mammal a = Cow();
Mammal b = Sheep();

//canSpeak()方法是继承的
println("a.canSpeak() : " + a.canSpeak());
println("b.canSpeak() : " + b.canSpeak());

//下面两个的叫声会不同,在运行期动态绑定方法
a.speak(); //打印牛叫
b.speak(); //打印羊叫

子类型(subtype)

面向对象编程时,我们可以给某个类创建不同的子类,实现一些个性化的功能;写程序时,我们可以站在抽象度更高的层次上,不去管具体的差异。

子类型的核心是提供了 is-a 的操作。也就是对某个类型所做的所有操作都可以用子类型替代。它可以放宽对类型的检查,从而导致多态。

子类型两种实现方式

  • 名义子类型(Nominal Subtyping):JavaC++ 语言
    • 显式声明继承了什么类,或者实现了什么接口
  • 结构化子类型(Structural Subtyping)
    • 一个类不需要显式地说自己是什么类型,只要它实现了某个类型的所有方法

语义分析

  1. 从类型处理的角度出发,我们要识别出新的类型:MammalCowSheep
  2. 设置正确的作用域
  3. 对变量和函数做类型的引用消解

Mammal 来声明这两个变量 a,b。按照类型推导的算法,a 和 b 都是 Mammal,这是个 I 属性计算的过程。也就是说,在编译期,我们无法知道变量被赋值的对象确切是哪个子类型,只知道声明变量时,它们是哺乳动物类型,至于是牛还是羊,就不清楚了。

正确的消解,是要指向 Cow 和 Sheep 的 speak 方法,只能到运行期再解决。

在运行期实现方法的动态绑定

在运行期,我们能知道 a 和 b 这两个变量具体指向的是哪个对象,对象里是保存了真实类型信息的。ClassObject 的 type 属性会指向一个正确的 Class,这个类型信息是在创建对象的时候被正确赋值的。

实现多态

在调用类的属性和方法时,我们可以根据运行时获得的,确定的类型信息进行动态绑定。下面这段代码是从本级开始,逐级查找某个方法的实现,如果本级和父类都有这个方法,那么本级的就会覆盖掉父类的。

protected Function getFunction(String name, List<Type> paramTypes){
//在本级查找这个这个方法
Function rtn = super.getFunction(name, paramTypes);

//如果在本级找不到,那么递归的从父类中查找
if (rtn == null && parentClass != null){
rtn = parentClass.getFunction(name,paramTypes);
}

return rtn;
}

获取类型信息,这种机制就叫做运行时类型信息(Run Time Type Information, RTTI)。C++、Java 等都有这种机制,比如 Java 的 instanceof 操作,就能检测某个对象是不是某个类或者其子类的实例。

继承情况下对象的实例化

不仅要初始化自己这一级的属性变量,还要把各级父类的属性变量也都初始化。

//从父类到子类层层执行缺省的初始化方法,即不带参数的初始化方法
protected ClassObject createAndInitClassObject(Class theClass) {
ClassObject obj = new ClassObject();
obj.type = theClass;

Stack<Class> ancestorChain = new Stack<Class>();

// 从上到下执行缺省的初始化方法
ancestorChain.push(theClass);
while (theClass.getParentClass() != null) {
ancestorChain.push(theClass.getParentClass());
theClass = theClass.getParentClass();
}

// 执行缺省的初始化方法
StackFrame frame = new StackFrame(obj);
pushStack(frame);
while (ancestorChain.size() > 0) {
Class c = ancestorChain.pop();
defaultObjectInit(c, obj);
}
popStack();

return obj;
}

this 和 super

public class ThisSuperTest {

public static void main(String args[]){
//创建Cow对象的时候,会在Mammal的构造方法里调用this.reportWeight(),这里会显示什么
Cow cow = new Cow();

System.out.println();

//这里调用,会显示什么
cow.speak();
}
}

class Mammal{
int weight;

Mammal(){
System.out.println("Mammal() called");
this.weight = 100;
}

Mammal(int weight){
this(); //调用自己的另一个构造函数
System.out.println("Mammal(int weight) called");
this.weight = weight;

//这里访问属性,是自己的weight
System.out.println("this.weight in Mammal : " + this.weight);

//这里的speak()调用的是谁,会显示什么数值
this.speak();
}

void speak(){
System.out.println("Mammal's weight is : " + this.weight);
}
}


class Cow extends Mammal{
int weight = 300;

Cow(){
super(200); //调用父类的构造函数
}

void speak(){
System.out.println("Cow's weight is : " + this.weight);
System.out.println("super.weight is : " + super.weight);
}
}

// result
Mammal() called
Mammal(int weight) called
this.weight in Mammal : 200
Cow's weight is : 0
super.weight is : 200

Cow's weight is : 300
super.weight is : 200