java继承涉及的动/静态绑定及隐藏

项目中经常会用到java多态这个特性,之前只知道一些皮毛,现在发现自己对它并没有一个系统的认识,想从新梳理下自己的基础库。

看了java编程思想中对象导论,关于继承的描述:java中的类型不仅仅只是描述了作用于一个对象集合上的约束条件,同时还有与其他类型的之间的关系可以创建一个基类来表示系统中某些对象的核心概念,从基类的基础上导出其他的类型,用以表示此核心可以被实现的不同方式。因此我们知道了类与类之间还有继承的关系。

问题:那什么是多态呢?多态跟继承有什么关系?

假设我编写了一个基类,它有多个导出类, 每次因我们调用导出类的方法而创建自己对象时,n多个导出类有可能创建n个导出类对象。

java中多态给我们带来的好处是可以不用创建具体的导出类对象,而是基类的对象,去调用各自的方法。这种情形在书中描述的是:在处理类型层次结构时,经常想把一个对象不当做它所属的特定类型对待,而是当做基类对象对待

例如:SAO是一个基类,AliSAO和WeixinSAO是其导出类,都有一个相同的hello方法

按照自己之前老套路就是:

AliSAO aliSAO = new AliSAO();

aliSAO .hello();

... ...

如果有n个导出类,就会有n个不同类型的对象

... ...

现在把导出类的类型改成基类,交由java这种多态,使得我们可以编写出不依赖特定类型的编码。  

SAO aliSAO = new AliSAO();
SAO weixinSAO = new WeixinSAO();

aliSAO.hello()和weixinSAO.hello()

aliSAO和weixinSAO对象可以统一用 SAO类型

这样的代码是受新添加代码影响较小。

但是这样子会引出一个问题:把导出类对象当做泛化的基类类型对象,此对象本身怎么确定去执行正确的方法呢?

一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定,编译器将产生一个具体的函数名称的调用,运行时这个调用会解析到具体代码的绝对地址上.而不会出现像java这种泛化对象的不确定性方法调用。

如果是private、static、final 方法或者是构造器,则编译器明确地知道要调用哪儿个方法,这种调用方式成为“静态调用”.动态绑定只是针对对象的非private,static,final方法.

而面向对象编程中,这种情形在编译期间是无法确定具体调用那个方法的,SAO类型对象(aliSAO和weixinSAO )在没有运行时不能确定它具体类型(有人觉得SAO aliSAO = new AliSAO();不就能知道是AliSAO类型泛化的,此时是在编译期间,对象都没有创建,虚拟机根本不知道,好比你自己心里想的自己很清楚,你想让别人知道,首先你得说出来,这个说出来就是运行),hello()方法都不知道调用自己的SAO对象是基类还是导出类。

为了解决这个问题面向对象程序设计语言提出了后期绑定的概念:java使用一小段代码代替了绝对地址调用,这段代码使用了对象中存储的信息来计算方法的地址.

 这样一来,每个对象根据这段代码的内容,可以具有不同的行为表现,当向一个对象发送消息时,该对象就能够做出相应的应答.

 上面说的都是实例方法,如果是类的静态属性和静态方法能否继承?

答案是可以的.只是这种继承有个特性,叫隐藏.当子类属性和方法跟父类相同时,会出现隐藏现象.如下图:

java的多态之所以能现实是依赖于父子类继承,接口实现,重写和重载.有了继承,可以通过子类对象指向父类引用,有了重写,可以通过父类引用的子类对象访问子类重写的方法,而不是父类的方法,这是因为“重写”后子类的方法优先级要高于父类的优先级.而隐藏却没有这个属性,因此在如图中通过Parent ps = new Son();ps.getName()是非静态方法,调用的是子类重写的方法,ps.getAge()是静态方法,只能调用父类的方法!

切记上面说的导出类继承基类方法的覆盖后,导出类的非静态的方法优先级要高,如果是属性呢?

如果注意到了:

Parent ps = new Son();
System.out.println(ps.age);// 返回父类的age=30

System.out.println(ps.money);// 返回父类的money=500

System.err.println(ps.name);// 'jack'

其中age和money在基类和导出类中都是静态变量.而name是非静态变量.

就会知道基类与导出类的属性覆盖是没有优先级的,获取的都是基类的属性值.它跟具体对象类型是没关系的,只跟对象引用类型有关!

重写只是发生在父子类继承的的方法中,属性是没有重写这一说法的.当导出类有和基类相同 名称的属性时(甚至类型都可以不同)基类的属性会被隐藏

 

对于子类来说,父类的属性是不能被子类对象引用访问到的,而需要通过其父类对象的引用访问;通常来说,我们不建议隐藏属性,因为这会使代码不易阅读;

 从以上定义可以看出,成员属性不能像方法那样被重写,当子类定义了一个和其父类相同名字的成员属性,子类仅仅是声明了一个新的属性,而其父类的属性被隐藏起来,这不是重写,实际上用super.属性名,还是可以得到父类的非private属性,所以不能以多态的形式访问。

 用一句话总结:在Java中,属性绑定到类型,方法绑定到对象!

用自己的话理解隐藏就是:当导出类中有与基类中属性相同名称或者private,static,final或构造方法等签名(方法名称+方法参数)相同时,注意前提条件是两者有继承关系且基类引用是由导出类对象实例化,如果不是有导出类对象实例化基类类型,则不会出现此情况.隐藏相当于导出类的实例对象去掉了基类的属性和方法,如下图,即对象不具备这种能力了,只能去访问基类对应的属性或方法!

效果类似:

 

向上转型:将导出类看成是基类的过程就是向上转型,前面提到的SAO aliSAO = new AliSAO();即是向上转型,其实际对象是AliSAO导出类的对象,引用的却是是基类.

只能导出类对象向上转型基类对象,反之则不能(如果基类对象能转型成导出类对象,会出现该对象丢失导出类的行为或属性,毕竟导出类的属性和方法要等于或多于基类).

 

 

 

 

posted @ 2017-03-27 23:17  chen_yong  阅读(648)  评论(0编辑  收藏  举报