项目中经常会用到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导出类的对象,引用的却是是基类.

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

java继承涉及的动/静态绑定及隐藏的更多相关文章

  1. Java继承之方法重写

    目录 Java继承之方法重写 代码体现 概念 注意事项 "两同两小一大" 其他注意点 重写与重载 @Override注解 Java继承之方法重写 在Java继承中,子类可以获得父类 ...

  2. 「万字图文」史上最姨母级Java继承详解

    摘要:继承是面向对象软件技术中的一个概念.它使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用. 本文分享自华为云社区<「万字图文」史上最姨母级Java继承详解丨[奔跑吧!JAVA] ...

  3. Java 继承02

    向上类型转换 父类型的引用指向子类型的实例. Person p = new Person();Animal a = p; //子类对象赋值给父类类型的变量 注意: 向上转型后,子类单独定义的方法会丢失 ...

  4. Java继承与组合

    Java继承与组合 继承 java 中使用extends关键字表示继承关系,当创建一个类时,如果没有明确指出要继承的类,则是隐式地从根类Object进行继承. 子类继承父类的成员变量 子类能够继承父类 ...

  5. Java—继承、封装、抽象、多态

    类.对象和包 1) 面向对象编程(Object Oriented Programming ,简称 OOP):20世纪70年代以后开始流行. 2) 结构化编程与面向对象编程的区别: A. 在结构化编程中 ...

  6. Java - 20 Java 继承

    Java 继承 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类.继承可以理解为一个对象从另一个对象获取属性的过程. 如果类A是类B的父类,而类B是类C的父类,我们也称C是A的子 ...

  7. Java继承相关知识总结

    Java继承的理解 一.概念: 一个新类从已有的类那里获得其已有的属性和方法,这种现象叫类的继承 这个新类称为子类,或派生类,已有的那个类叫做父类,或基类 继承的好处:代码得到极大的重用.形成一种类的 ...

  8. Java 继承和多态

                                                        Java  继承和多态 Java 继承 继承的概念 继承是java面向对象编程技术的一块基石,因 ...

  9. 【笔试题】Java 继承知识点检测

    笔试题 Java 继承知识点检测 Question 1 Output of following Java Program? class Base { public void show() { Syst ...

随机推荐

  1. Java重定向和转发的路径问题

      路径问题:         ①相对路径和绝对路径:             绝对路径:  绝对路径是以/开头的路径!             相对于当前服务器的绝对路径: 如果是服务器解析,那么/ ...

  2. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(二)用ffmpeg解码音频

    其实这篇的内容和(一)用ffmpeg解码视频基本是一样的,重点还是给ffmpeg指定callback函数,而这个函数是从RTSP服务端那里获取音频数据的. 这里,解码音频的示例代码量之所以比解码视频的 ...

  3. c#获取数组中最大的元素

    , , , , , , , , , }; var max = array.Max();//获取数组中的最大值 第一种 //第二种方法 ]; ; i < array.Length; i++) { ...

  4. php 开发调试的常用技巧和工具​

      nginx的access.log 作用: 记录一个页面请求中,服务器接收到的所用相关的url. 根据此log, 对服务器上调用了哪些服务,可以一目了然,快速理清业务关系,调试开发非常赞! 注意: ...

  5. win7怎么更换锁屏壁纸

    win7怎么更换锁屏壁纸... -------------------------- 按键盘组合键“Windows+R”可打开“运行”窗口,输入 “gpedit.msc” 按回 ----------- ...

  6. 卸载oracle 11g数据库

    完全卸载oracle11g步骤:1. 开始->设置->控制面板->管理工具->服务 停止所有Oracle服务.2. 开始->程序->oracle - OraHome ...

  7. mongodb菜鸟整理

    一,mongodb的安装与连接 从官网下载后,解压或者安装到某个目录下 1  首先需要自己创建一个db文件夹 ,用于存放数据库的数据 然后还需要创建一个log文件夹,里面需要自己创建一个mongodb ...

  8. 利用Google浏览器调试js代码

    1.js有两种引入方式,外链和内嵌: 内嵌在浏览中直接调试,外链要在断点处写debugger; 示例代码: <!DOCTYPE html> <html lang="en&q ...

  9. json对象和json字符串之间的转化

    json对象和json字符串之间的转化 json字符串----->json对象 使用JSON.parse()函数 var jsonStr = '{"name":"z ...

  10. vue 父子组件传参

    父向子组件传参 例子:App.vue为父,引入componetA组件之后,则可以在template中使用标签(注意驼峰写法要改成componet-a写法,因为html对大小写不敏感,component ...