前两篇博文,分别介绍了java的面向对象的两大特性封装和继承,这一篇为介绍一下多态。

首先回顾一下什么是封装、继承。

封装就是隐藏信息,使用抽象的数据类型将数据以及基于数据的操作封装起来,使得外界不能够知道类中的内部细节,只提供给外部访问的方法。

继承就是将共有的属性封装成一个类,使之成为父类,如果编写的其他类使用父类的属性和方法,就让这些类继承于父类,成为子类。子类和父类具有IS-A的关系,继承为多态做下了铺垫。下面介绍一下什么是多态:

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是

在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实

现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以

让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序

运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:

酒 a = 剑南春

酒 b = 五粮液

酒 c = 酒鬼酒

这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。

诚然,要理解多态我们就必须要明白什么是“向上转型”。在继承中我们简单介绍了向上转型,这里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义如下代码:

JNC a = new JNC();

对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?

Wine a = new JNC();

在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。

但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了---1。

[java]  view plain copy
  1. public class Wine {
  2. public void fun1(){
  3. System.out.println("Wine 的Fun.....");
  4. fun2();
  5. }
  6. public void fun2(){
  7. System.out.println("Wine 的Fun2...");
  8. }
  9. }
  10. public class JNC extends Wine{
  11. /**
  12. * @desc 子类重写父类方法
  13. *        父类中不存在该方法,向上转型后,父类是不能引用该方法的
  14. * @param a
  15. * @return void
  16. */
  17. public void fun1(String a){
  18. System.out.println("JNC 的 Fun1...");
  19. fun2();
  20. }
  21. /**
  22. * 子类重写父类方法
  23. * 指向子类的父类引用调用fun2时,必定是调用该方法
  24. */
  25. public void fun2(){
  26. System.out.println("JNC 的Fun2...");
  27. }
  28. }
  29. public class Test {
  30. public static void main(String[] args) {
  31. Wine a = new JNC();
  32. a.fun1();
  33. }
  34. }
  35. -------------------------------------------------
  36. Output:
  37. Wine 的Fun.....
  38. JNC 的Fun2...

从程序的运行结果中我们发现,a.fun1()首先是运行父类Wine中的fun1().然后再运行子类JNC中的fun2()。

分析:在这个程序中子类JNC重载了父类Wine的方法fun1(),重写fun2(),而且重载后的fun1(String a)与 fun1()不是同一个方法,由于父类中没有该方法,向上转型后会丢失该方法,所以执行JNC的Wine类型引用是不能引用fun1(String a)方法。而子类JNC重写了fun2() ,那么指向JNC的Wine引用会调用JNC中fun2()方法。

所以对于多态我们可以总结如下:

指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。

对于面向对象而已,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

多态的实现

2.1实现条件

在刚刚开始就提到了继承在为多态的实现做了准备。子类Child继承父类Father,我们可以编写一个指向子类的父类类型引用,该引用既可以处理父类Father对象,也可以处理子类Child对象,当相同的消息发送给子类或者父类对象时,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。即多态性就是相同的消息使得不同的类做出不同的响应。

Java实现多态有三个必要条件:继承、重写、向上转型。

继承:在多态中必须存在有继承关系的子类和父类。

重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

2.2实现形式

在Java中有两种形式可以实现多态。继承和接口。

2.2.1、基于继承实现的多态

基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。

[java]  view plain copy
  1. public class Wine {
  2. private String name;
  3. public String getName() {
  4. return name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public Wine(){
  10. }
  11. public String drink(){
  12. return "喝的是 " + getName();
  13. }
  14. /**
  15. * 重写toString()
  16. */
  17. public String toString(){
  18. return null;
  19. }
  20. }
  21. public class JNC extends Wine{
  22. public JNC(){
  23. setName("JNC");
  24. }
  25. /**
  26. * 重写父类方法,实现多态
  27. */
  28. public String drink(){
  29. return "喝的是 " + getName();
  30. }
  31. /**
  32. * 重写toString()
  33. */
  34. public String toString(){
  35. return "Wine : " + getName();
  36. }
  37. }
  38. public class JGJ extends Wine{
  39. public JGJ(){
  40. setName("JGJ");
  41. }
  42. /**
  43. * 重写父类方法,实现多态
  44. */
  45. public String drink(){
  46. return "喝的是 " + getName();
  47. }
  48. /**
  49. * 重写toString()
  50. */
  51. public String toString(){
  52. return "Wine : " + getName();
  53. }
  54. }
  55. public class Test {
  56. public static void main(String[] args) {
  57. //定义父类数组
  58. Wine[] wines = new Wine[2];
  59. //定义两个子类
  60. JNC jnc = new JNC();
  61. JGJ jgj = new JGJ();
  62. //父类引用子类对象
  63. wines[0] = jnc;
  64. wines[1] = jgj;
  65. for(int i = 0 ; i < 2 ; i++){
  66. System.out.println(wines[i].toString() + "--" + wines[i].drink());
  67. }
  68. System.out.println("-------------------------------");
  69. }
  70. }
  71. OUTPUT:
  72. Wine : JNC--喝的是 JNC
  73. Wine : JGJ--喝的是 JGJ
  74. -------------------------------

在上面的代码中JNC、JGJ继承Wine,并且重写了drink()、toString()方法,程序运行结果是调用子类中方法,输出JNC、JGJ的名称,这就是多态的表现。不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。

我们都知道所有的类都继承自超类Object,toString()方法也是Object中方法,当我们这样写时:

[html]  view plain copy
  1. Object o = new JGJ();
  2. System.out.println(o.toString());

输出的结果是Wine : JGJ。

Object、Wine、JGJ三者继承链关系是:JGJ—>Wine—>Object。所以我们可以这样说:当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用。但是注意如果这样写:

[java]  view plain copy
  1. Object o = new Wine();
  2. System.out.println(o.toString());

输出的结果应该是Null,因为JGJ并不存在于该对象继承链中。

所以基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。

如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。

2.2.2、基于接口实现的多态

继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。

在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。

继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

java基础提高篇--java的三大特性--多态相关推荐

  1. java基础学习篇——java学习目录与应用场景

    两个方向--大数据开发.javaEE 技术拓展: 数据结构,常用算法,常用设计模式,JVM JAVAEE之路 javase--javaweb--javaee框架 (spring.springmvc.s ...

  2. Java提高篇 —— Java三大特性之继承

    一.前言 在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事 ...

  3. Java面向对象编程篇2——面向对象三大特点

    Java面向对象编程篇2--面向对象三大特点 1.封装 1.1.封装的概念 通常情况下可以在测试类给成员变量赋值一些合法但不合理的数值,无 论是编译阶段还是运行阶段都不会报错或者给出提示,此时与现实生 ...

  4. java入门提高篇:Day1 抽象类

    java入门提高篇:Day1 抽象类 基础部分内容差不多讲解完了,今天开始进入Java提高篇部分,这部分内容会比之前的内容复杂很多,希望大家做好心理准备,看不懂的部分可以多看两遍,仍不理解的部分那一定 ...

  5. Java番外篇2——jdk8新特性

    Java番外篇2--jdk8新特性 1.Lambda 1.1.无参无返回值 public class Test {interface Print{void print();}public static ...

  6. JAVA中整型常量的长度,Java基础入门篇(三)——Java常量、变量,

    Java基础入门篇(三)--Java常量.变量, 一.Java常量 (一)什么是常量 常量指的是在程序中固定不变的值,是不能改变的数据.在Java中,常量包括整型常量.浮点型常量.布尔常量.字符常量等 ...

  7. 超详细的Java面试题总结(二)之Java基础知识篇

    系列文章: 超详细的Java面试题总结(一)之Java基本知识 超详细的Java面试题总结(二)之Java基础知识篇 超详细的Java面试题总结(三)之Java集合篇常见问题 超详细的Java面试题总 ...

  8. 打怪升级之小白的大数据之旅(一)<Java基础语法之Java的身世之谜>

    打怪升级之小白的大数据之旅(一) Java基础语法之Java的身世之谜 打怪升级之小白的大数据之旅(一) 前言 一.学习大数据之前 二.Java基础 what? why? how? 总结 前言 做了几 ...

  9. Java基础2019最新Java面试经典题解析

    Java基础2019最新Java面试经典题解析 1简述JVM.JRE.JDK的区别 JVM:java虚拟机 ,加载.class并运行.class JRE:java运行环境除了包含JVM以外还包含了运行 ...

最新文章

  1. [故障引起的故事]URL中带加号(+)的处理
  2. C# 数据类型映射 (SQLite,MySQL,MSSQL,Oracle)
  3. JavaScript实现搜索框效果
  4. Windows 使用命令执行 sql 脚本文件
  5. AES加密例子(python和php版本)
  6. 【python】*与** 参数问题
  7. 商城项目测试用例设计实战
  8. 大猫谈JNCIE实验考试备战
  9. 天地图行政区划描边_百度地图api-在中国范围内的多行政区选择与反选(增加屏蔽层)...
  10. C语言基础练习-输入球体半径,计算球体表面积和体积
  11. 如何用AI快速绘制大鼠模型及相关技术路线图,超详细教程!
  12. 计算机c盘哪些可以清理,怎么清理C盘?不知道哪些可以删除?
  13. 常见网络协议总结(五层自顶向下)
  14. 蓝桥杯比赛准备总结(大学编程学习历程)
  15. L版本通话记录里面显示的通话时长与实际不一致的问题
  16. Java Web 后台中餐饮业报表系统开发实例
  17. Copyright ©的含义
  18. python基础-input函数
  19. MongoDB增删改查基础操作
  20. linux 端口监听 Netstat 常用命令

热门文章

  1. 普及ARM架构芯片的知识
  2. C++核心编程【只讲解C++语言特性,需要有C语言基础】
  3. 解决Ubuntu报错 E: Unable to locate package yum
  4. LCP 18.早餐组合
  5. HTML-2.HTML入门—如何在IntellijIDEA中新建一个HTML项目
  6. 日出日落 matlab,如何计算日出和日落时间(matlab)?
  7. rs485无线通讯模块在工业测控中的应用
  8. python身份证图片倾斜矫正_python实现证件图片倾斜矫正
  9. halcon倾斜字体矫正(举一反三)
  10. 阻塞/非阻塞——纸上得来终觉浅,绝知此事要躬行