面向对象之 继承关系中的关系

重写,隐藏,以及父子间的多态(或者叫兼容方法)

 

最最开始的时候

明确一下

实例化子类的时候

只是实例化了一个子类对象

并没有实例化它的父类

不过子类对象包含有从父类继承的方法属性

粗糙的概念就是,在分配空间的时候,子类本身所需的内存加上所有父类所需的内存的

 

回归正题谈继承关系

 

根据开闭原则

子类通过向上转换来表现继承的关系

接口用父类定义

然后传入不同实现的子类来实现多态

这是父子统一的一面

但是子类的存在本身是有个性的

当需要针对个性做特殊处理的时候

比如都是多边形

但是我需要三角形的用红色显示

这时候就需要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内存区域

6, JAVA构造函数、覆盖、隐藏

7, Java 隐藏与覆盖函数 构造函数

8,图解Java多态内存分配以及多态中成员方法的特点

9,图解Java继承内存分配

10,我们为什么需要override关键字

11,使用 Override 和 New 关键字进行版本控制(C# 编程指南)

12,子类为什么不能重写父类的静态方法

 

13,子类重写父类的方法时声明抛出异常不能比父类范围大

 

 

posted on 2014-05-28 04:04  tirestay  阅读(424)  评论(0编辑  收藏  举报

导航