重写,隐藏,以及父子间的多态(或者叫兼容方法)
最最开始的时候
明确一下
实例化子类的时候
只是实例化了一个子类对象
并没有实例化它的父类
不过子类对象包含有从父类继承的方法属性
粗糙的概念就是,在分配空间的时候,子类本身所需的内存加上所有父类所需的内存的
回归正题谈继承关系
根据开闭原则
子类通过向上转换来表现继承的关系
接口用父类定义
然后传入不同实现的子类来实现多态
这是父子统一的一面
但是子类的存在本身是有个性的
当需要针对个性做特殊处理的时候
比如都是多边形
但是我需要三角形的用红色显示
这时候就需要RTTI来检验一个多边形是否是三角形
什么是RTTI
展开是Run-Time Type Identification
就是运行时类型识别
这是一个机制,它维护着类的相关信息,
拿JAVA来说
它在每个类实例化的同时实例一个Class对象,每个类可以通过getClass来得到这个Class对象
这个Class对象有什么特征呢
它保留着类的原始信息
原始信息的意思就是,比如这个类在实例化的时候是什么类型。这个信息在它整个生命周期保持同一份,这样才不会因为向上转型而迷失
TSanjiao sanjiao = new TSanjiao();
Class对象中保存的是TSanjiao类型
TDuobian duobian= (TDuobian)sanjiao;
转变为TDuobian后呢,Class对象中保存的还是TSanjiao类型
上面的也等同于
TDuobian duobian= new TSanjiao();
不管怎么转变,我初始化的时候是什么样的,我这一生都将是怎么样的
if( duobian instanceof TSanjiao ){ //红色显示 }
这就是RTTI的一个用法
duobian这个实例对应的Class对象中保留了类型信息
通过instanceof来了解这个对象是否是三角形这个特殊类型,如果是,红色显示
再回头来看继承
RTTI确保在继承的体系中不会迷失
也就是说,对于这个调用:
public show(TDuobian duobian){ duobian.show(); }
如果传进来的是个三角形,那么应该调用 子类三角形中的show()
如果传进来的是个四边形,那么应该调用 子类四边形中的show()
这是重写的机制
也是多态的意义
或者说
这是正常意义上的继承体系中应该出现的表现: 根据传进来的对象的原始类型动态调用对应的方法,不管它转为什么类型
也就是说
重写是子类重写了父类的方法,
从规范上来说,子类这个对象里只有一份被重写过的方法
从具体实现来说,在子类的内存方法区中,子类只有一个被重写过的方法体
那么
反过来,如果我转为什么类型就调用什么类型的方法,而不管这个对象的原始类型呢
这就是隐藏
隐藏就是说,不管你传进来的是三角形四边形,我都调用TDuobian多边形的show方法,我把你们具体子类的方法给隐藏掉
也就是说,
隐藏是父类隐藏了子类的方法
从规划上来说,这是一个非正常操作,因为失去了继承的意义,虽然语法没问题,但是无视设计规范
从具体实现上来说,在各个面向对象体系中不同
在Java中,不会出现隐藏的情况,因为方法默认都是可重写的,父子签名相同的方法就是重写。- 这里注意@Override注解,这只是个注解,1,为了发现拼写错误,2,为了语义明确,一看就知道注解的方法是重写父类的。在Java中是通过方法签名,而不是有没有加@Override来表明是否是重写
在C#中,却是有隐藏的,此时在子类的内存方法区中,有一个属于从父类继承的方法,和一个子类自己的方法。这样会根据对象引用的指针类型来调用对应的方法,而不是根据实际对象的类型来调用,所以隐藏的时候不会考虑到RTTI。C#推荐用new关键字来标明这种情况。就跟上面的@Override一样,只是推荐,只是一种让语义更加明确的做法。不加没有问题,不加也是隐藏,不加只会提示你子类的方法隐藏了父类的方法
看例子,重写和隐藏在代码里怎么表示
父类:
public class TFather { public virtual void testOverride(TFather words) { System.Console.WriteLine("[father] testOverride"); } public void testHide(TFather words) { System.Console.WriteLine("[father] testHide"); } }
子类:
public class TSon : TFather { public override void testOverride(TFather words) { System.Console.WriteLine("[son] testOverride"); } public void testHide(TFather words) { System.Console.WriteLine("[son] testHide"); } }
调用:
static void Main(string[] args) { TFather father = new TFather(); TSon son = new TSon(); son.testOverride(father); ((TFather)son).testOverride(father); son.testHide(father); ((TFather)son).testHide(father); System.Console.ReadLine(); }
父类TParent有方法叫testOverride, testHide
子类TSon对应两个方法,
重写了方法testOverride,
隐藏了方法testHide,
在客户端代码中(main方法里),我们实例化一个子类指向son
我们可以想象,在son这个实例的方法区中可以看到三个方法 - 这里借用TParent和TSon来指定方法所属来源,实际上并不存在
TSon::testOverride, 重写过的testOverride方法
TParent::testHide, 继承自父类的testHide方法
TSon::testHide, 隐藏了父类同名方法的testHide方法
我们看到在此实例的方法区中是找不到TParent::testOverride的,因为它被重写了,保留下来的只有子类的testOverride()方法
当我们调用testOverride的时候,不管son转变为什么类型,调用的都是TSon::testOverride(),因为只有一份testOverride()方法
当我们调用testHide()的时候,当son转变为什么类型,就调用对应类型的方法
比如
son.testHide()调用的就是TSon::testHide()
((TParent)son).testHide()调用的就是TParent::testHide()
所以上面的输出是:
[son] testOverride [son] testOverride [son] testHide [father] testHide
这里总结下,什么是重写,什么是隐藏
重写,Override, 是指派生类函数覆盖基类函数,有三个特征:
(1)函数名字相同;
(2)参数相同;
(3)基类函数必须有virtual 关键字,子类带override关键字 - C#。子类函数带@Override注解(不是必须) - java。
隐藏,Hide, 是指派生类的函数屏蔽了与其同名的基类函数,两种情况:
(1)函数名字相同,但是参数不同。
(2)函数名字相同,并且参数也相同,但是子类没有覆盖父类的话(就是基类函数没有virtual 关键字,或者子类有new关键字-C#)。
接下来看看一种特殊的多态,在调用的时候对应父子类中的多个兼容方法。这种情况下,兼容方法的入参是同一个继承链上的
此时在C#和Java中的处理是不一样的
在C#中碰到兼容的方法时,先在当前类型的方法中寻找,然后在父类继承的方法中寻找
父类:
public class TFather { public void testPolymorphic1(TSon words) { System.Console.WriteLine("[father] testPolymorphic1"); } public void testPolymorphic2(TFather words) { System.Console.WriteLine("[father] testPolymorphic2"); } public void testPolymorphic3(TFather words) { System.Console.WriteLine("[father] testPolymorphic3"); } }
子类:
public class TSon : TFather { public void testPolymorphic1(TFather words) { System.Console.WriteLine("[son] testPolymorphic1"); } public void testPolymorphic2(TSon words) { System.Console.WriteLine("[son] testPolymorphic2"); } public void testPolymorphic3(TGrandson words) { System.Console.WriteLine("[son] testPolymorphic3"); } }
调用:
static void Main(string[] args) { TFather father = new TFather(); TSon son = new TSon(); TGrandson grandson = new TGrandson(); son.testPolymorphic1(father); son.testPolymorphic1(son); son.testPolymorphic1(grandson); //((TFather)son).testPolymorphic1(father); -> 这是没有的 ((TFather)son).testPolymorphic1(son); ((TFather)son).testPolymorphic1(grandson); System.Console.WriteLine("=============================="); son.testPolymorphic2(father); son.testPolymorphic2(son); son.testPolymorphic2(grandson); ((TFather)son).testPolymorphic2(father); ((TFather)son).testPolymorphic2(son); ((TFather)son).testPolymorphic2(grandson); System.Console.WriteLine("=============================="); son.testPolymorphic3(father); son.testPolymorphic3(son); son.testPolymorphic3(grandson); ((TFather)son).testPolymorphic3(father); ((TFather)son).testPolymorphic3(son); ((TFather)son).testPolymorphic3(grandson); System.Console.ReadLine(); }
输出如下:
1,[son] testPolymorphic1 2,[son] testPolymorphic1 3,[son] testPolymorphic1 4,[father] testPolymorphic1 5,[father] testPolymorphic1 ============================== 6,[father] testPolymorphic2 7,[son] testPolymorphic2 8,[son] testPolymorphic2 9,[father] testPolymorphic2 10,[father] testPolymorphic2 11,[father] testPolymorphic2 ============================== 12,[father] testPolymorphic3 13,[father] testPolymorphic3 14,[son] testPolymorphic3 15,[father] testPolymorphic3 16,[father] testPolymorphic3 17,[father] testPolymorphic3
对于 4,5,9,10,11,15,16,17 这八种情况,因为son都被强转为TFather类型,所以调用的都是父类的方法,除非他还有父类,这里不会再去TSon类中寻找兼容方法
对于 1, 在TFather中是TSon,在TSon中是TFather,实际参数是TFather,当前参数是TSon,所以先在TSon中找,TFather->TFather,类型一致,所以选择TSon中的方法
对于 2, 在TFather中是TSon,在TSon中是TFather,实际参数是TSon,当前参数是TSon,所以先在TSon中找,TSon->TFather,转换安全,所以选择TSon中的方法
对于 3,在TFather中是TSon,在TSon中是TFather,实际参数是TGrandson,当前参数是TSon,所以先在TSon中找,TGrandson->TFather,转换安全,所以选择TSon中的方法
对于 6 ,在TFather中是TFahter,在TSon中是TSon,实际参数是TFather,当前参数是TSon,所以先在TSon中找,TFather->TSon,不安全,然后在父类中找,TFather->TFather,类型一致,所以调用TFather的方法
对于 7 ,在TFather中是TFahter,在TSon中是TSon,实际参数是TSon,当前参数是TSon,所以先在TSon中找,TSon->TSon,类型一致,选择TSon的方法
对于 8 ,在TFather中是TFahter,在TSon中是TSon,实际参数是TGrandson,当前参数是TSon,所以先在TSon中找,TGrandson->TSon,安全的转换,所以选择TSon的方法
对于12,在TFather中是TFahter,在TSon中是TGrandson,实际参数是TFather,当前参数是TSon,所以在TSon中找,TFather->TGrandson,不安全,然后在TFather中找,TFather->TFather,类型一致,所以选择TFather的方法
对于13,在TFather中是TFahter,在TSon中是TGrandson,实际参数是TSon,当前参数是TSon,所以在TSon中找,TSon->TGrandson,不安全,然后在TFather中找,TSon->TFather,安全的转换,所以选择TFather的方法
对于14,在TFather中是TFahter,在TSon中是TGrandson,实际参数是TGrandson,当前参数是TSon,所以在TSon中找,TGrandson->TGrandson,类型一致,所以选择TSon的方法
在Java中碰到兼容的方法时,在当前类型整个方法表中(包含从父类继承的方法)寻找最兼容的方法
父类:
public class TFather { public void testPolymorphic1(TSon words) { System.out.println("[father] testPolymorphic1"); } public void testPolymorphic2(TFather words) { System.out.println("[father] testPolymorphic2"); } public void testPolymorphic3(TFather words) { System.out.println("[father] testPolymorphic3"); } }
子类:
public class TSon extends TFather { public void testPolymorphic1(TFather words) { System.out.println("[son] testPolymorphic1"); } public void testPolymorphic2(TSon words) { System.out.println("[son] testPolymorphic2"); } public void testPolymorphic3(TGrandson words) { System.out.println("[son] testPolymorphic3"); } }
调用:
public static void main(String[] argv){ TFather father = new TFather(); TSon son = new TSon(); TGrandson grandson = new TGrandson(); son.testPolymorphic1(father); son.testPolymorphic1(son); son.testPolymorphic1(grandson); //((TFather)son).testPolymorphic1(father); -> 这是没有的 ((TFather)son).testPolymorphic1(son); ((TFather)son).testPolymorphic1(grandson); System.out.println("=============================="); son.testPolymorphic2(father); son.testPolymorphic2(son); son.testPolymorphic2(grandson); ((TFather)son).testPolymorphic2(father); ((TFather)son).testPolymorphic2(son); ((TFather)son).testPolymorphic2(grandson); System.out.println("=============================="); son.testPolymorphic3(father); son.testPolymorphic3(son); son.testPolymorphic3(grandson); ((TFather)son).testPolymorphic3(father); ((TFather)son).testPolymorphic3(son); ((TFather)son).testPolymorphic3(grandson); }
输出:
1,[son] testPolymorphic1 2,[father] testPolymorphic1 3,[father] testPolymorphic1 4,[father] testPolymorphic1 5,[father] testPolymorphic1 ============================== 6,[father] testPolymorphic2 7,[son] testPolymorphic2 8,[son] testPolymorphic2 9,[father] testPolymorphic2 10,[father] testPolymorphic2 11,[father] testPolymorphic2 ============================== 12,[father] testPolymorphic3 13,[father] testPolymorphic3 14,[son] testPolymorphic3 15,[father] testPolymorphic3 16,[father] testPolymorphic3 17,[father] testPolymorphic3
对于 4,5,9,10,11,15,16,17 这五种情况,因为当前类型是TFather,所以只能在TFather中找兼容方法
对于 1,在TFather中是TSon,在TSon中是TFather,实际参数是TFather,当前参数是TSon,在TSon的方法表中最兼容的是TSon,类型一致
对于 2,在TFather中是TSon,在TSon中是TFather,实际参数是TSon,当前参数是TSon,在TSon的方法表中最兼容的是TFather,类型一致
对于 3,在TFather中是TSon,在TSon中是TFather,实际参数是TGrandson,当前参数是TSon,在TSon的方法表中最兼容的是TFather,TGrandson->TSon,虽然TSon的方法也能兼容并且转换安全,但是TGrandson->TFather的转换路径更长
对于 6,在TFather中是TFahter,在TSon中是TSon,实际参数是TFather,当前参数是TSon,在TSon的方法表中最兼容的是TFather,入参是TFather,TFather中方法的入参是TFather,TSon中方法的入参是TSon,所以调用TFather中的方法
对于 7,在TFather中是TFahter,在TSon中是TSon,实际参数是TSon,当前参数是TSon,在TSon的方法表中最兼容的是TSon,类型一致
对于 8,在TFather中是TFahter,在TSon中是TSon,实际参数是TGrandson,当前参数是TSon,在TSon的方法表中最兼容的是TSon,TGrandson->TSon,虽然TFather的方法也能兼容并且转换安全,但是TGrandson->TFather的转换路径更长
对于 12,在TFather中是TFahter,在TSon中是TGrandson,实际参数是TFather,当前参数是TSon,在TSon的方法表中最兼容的是TFather的方法,类型一致
对于 13,在TFather中是TFahter,在TSon中是TGrandson,实际参数是TSon,当前参数是TSon,在TSon的方法表中最兼容的是TFather的方法,TSon->TFather,在TSon中的方法,因为TSon->TGrandson转换不安全而丢弃
对于 14,在TFather中是TFahter,在TSon中是TGrandson,实际参数是TGrandson,当前参数是TSon,在TSon的方法表中最兼容的是TSon的方法,类型一致
//TODO:
1, 引用6,7,12,13的总结,和变量的重写隐藏,重写中的构造函数,异常相关
参考:
1, Java进阶04 RTTI
2, java对RTTI的需要
3, java rtti学习总结
4, 继承的内存分配
5, Java内存管理:深入Java内存区域
7, Java 隐藏与覆盖函数 构造函数
11,使用 Override 和 New 关键字进行版本控制(C# 编程指南)
13,子类重写父类的方法时声明抛出异常不能比父类范围大