夯实Java基础(四)——面向对象之多态
1、多态介绍
面向对象三大特征:封装、继承、多态。多态是Java面向对象最核心,最难以理解的内容。从一定角度来看,封装和继承几乎都是为多态而准备的。
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
比如有一种动物,藏在某个看不见得地方,你知道它是一种动物,但是不知道具体是哪种动物,只有等它发出叫声才能辨别,是猫——喵喵喵,是狗——旺旺旺,你一听就知道这是什么动物,对于不同的动物会有不同的结果,可以理解为多态。
Java多态也可以用一句话表示:父类的引用指向子类的对象。
多态存在的三个必要条件
1、继承:在多态中必须存在有继承关系的子类和父类。
2、重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
3、向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
2、多态的实现
开头说了这么多Java多态的基本概念,那么到底用Java代码怎么体现出来呢?下面我们直接看代码。
public class Animal { public void eat(){ System.out.println("动物吃东西"); } public void skill(){ System.out.println("动物有本领"); } } class Dog extends Animal{ public void eat() { System.out.println("狗吃骨头"); } public void skill(){ System.out.println("狗会看家"); } } class Cat extends Animal{ public void eat() { System.out.println("猫吃鱼"); } public void skill(){ System.out.println("猫会抓老鼠"); } } //测试类 public class AnimalTest { public static void main(String[] args) { Animal dog=new Dog(); dog.eat(); dog.skill(); Animal cat=new Cat(); cat.eat(); cat.skill(); } }
运行结果:
从上面的测试类来看,我们都是创建不同子类的对象,相同的父类引用,却表现出它们不同的特征,这就是体现了Java的多态性。
如果你认为这样还是不能体现多态的好处,我们在AnimalTest类中添加一个show()方法,来体会Java多态的好处。
如果Java中没有多态的特性会是什么样的,下面我们来看一下。
public class AnimalTest { public static void main(String[] args) { AnimalTest animalTest=new AnimalTest(); animalTest.show(new Animal()); animalTest.show(new Dog()); animalTest.show(new Cat()); } public void show(Animal animal){ animal.eat(); animal.skill(); } public void show(Dog dog){ dog.eat(); dog.skill(); } public void show(Cat cat){ cat.eat(); cat.skill(); } }
可以发现在show()方法中的形参都传入了对象,而且重载3个相同的show()方法,如果我们有十个百个千个类需要传入方法,那么岂不是要重载上千个方法,可见这样代码的冗余非常的大,非常不利于代码的维护。
而如果有多态的话,只需写一个show()方法即可。
public class AnimalTest { public static void main(String[] args) { AnimalTest animalTest=new AnimalTest(); animalTest.show(new Animal()); animalTest.show(new Dog()); animalTest.show(new Cat()); } public void show(Animal animal){ animal.eat(); animal.skill(); } }
从这里可以看出来多态的优点:
1、减少重复代码,使代码变得简洁(由继承保证)。
2、提高了代码的扩展性(由多态保证)。
但是也有缺点:子类单独定义的方法会丢失。后面的向上转型会介绍到。
多态其实是一种虚拟方法调用。在编译期间,只能调用父类中声明的方法,但是在运行期间,实际执行的是子类重写父类的方法。
总结为一句话:编译看左边,运行看右边(可能看到这句话会有点头晕,但是理解下面向上转型的概念就应该能够理解这句话了)。
3、向上转型
子类引用的对象转换为父类类型称为向上转型。通俗地说就是是将子类对象转为父类对象。此处父类对象也可以是接口。
我们用前面多态的例子举例,只是在子类中添加了它们自己的方法,父类中没有定义,如下。
public class Animal { public void eat(){ System.out.println("动物吃东西"); } public void skill(){ System.out.println("动物有本领"); } } class Dog extends Animal{ public void eat() { System.out.println("狗吃骨头"); } public void skill(){ System.out.println("狗会看家"); } //新添加的方法 public void run(){ System.out.println("狗跑得快"); } } class Cat extends Animal{ public void eat() { System.out.println("猫吃鱼"); } public void skill(){ System.out.println("猫会抓老鼠"); } //新添加的方法 public void life(){ System.out.println("猫有九条命"); } } //测试类 public class AnimalTest { public static void main(String[] args) { Animal dog=new Dog();//向上转型成Animal dog.eat(); dog.skill(); //dog.run();//Cannot resolve method 'run()' Animal cat = new Cat();//向上转型成Animal cat.eat(); cat.skill(); //cat.life();//Cannot resolve method 'life()' } }
这里就产生了向上转型,Animal dog= new Dog();Animal cat= new Cat();将子类对象Dog和Cat转化为父类对象Animal。这个时候Animal这个引用调用的都是子类方法。再去调用子类单独的方法就会报错。如果非要调用也不是说不可以,那就要强转了。既然现在已经是父类了,那就强转为子类呗。
((Dog) dog).run(); ((Cat) cat).life();
这样也可以调用。但是千万要注意,不能这样转,子类引用不能指向父类对象,Dog dog=(Dog)newAnimal();Cat cat = (Cat)new Animal();这样是绝对不行的。就好像儿子可以生出爸爸一样,这样不和常理。
如果在向上转型时,子类并没有重写父类的方法,那么调用的就是父类中的方法。
到此为止,也可以证明前面说的一个结论:向上转型会使子类单独定义的方法会丢失。
4、向下转型
与向上转型相对应的就是向下转型了。向下转型是把父类对象转为子类对象。这里我们就会想到,即然子类向上转型为了父类,而子类又继承了父类的属性和方法,为什么还要将父类转型为子类。是因为对象的多态性只适用于方法,而不适用于属性。所以当我们在使用多态的时候,就不能调用子类中的属性和特有的方法了,所以需要向下转型。(内存中实际上是加载了子类所特有的属性和方法,但是由于变量声明的是父类类型,导致在编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用)
我们还是使用向上转型那里的代码为例(Anima、Dog、Cat类):
public class AnimalTest { public static void main(String[] args) { Animal dog=new Dog();//Dog向上转型成Animal Dog dog1= (Dog) dog;//向下转型为Dog dog1.eat(); dog1.skill(); Cat cat=(Cat) dog;//java.lang.ClassCastException: com.thr.java2.Dog cannot be cast to com.thr.java2.Cat cat.eat(); cat.skill(); } }
运行结果:
我们可以发现向下转型为Dog没有报错,但是转型为Cat却报错了,这个倒不难理解,因为开始向上转型本来是Dog,然后再变回Dog,总不能Dog变成Cat吧。所以会报类型转换错误。
向下转型注意事项
- 向下转型的前提是父类对象指向的是子类对象(也就是说,在向下转型之前,它得先向上转型)
- 向下转型只能转型为本类对象(猫是不能变成狗的)。
向下转型我们一般会使用 instanceof 关键字来判断:
使用方法:a instanceof A:判断对象a是否为对象A的实例,如果是,返回true,如果不是,则返回false。
public class AnimalTest { public static void main(String[] args) { AnimalTest test=new AnimalTest(); test.show(new Animal()); test.show(new Dog()); test.show(new Cat()); } public void show(Animal animal){ if (animal instanceof Dog){ Dog dog= (Dog) animal; dog.eat(); dog.skill(); dog.run(); } } }
运行结果:
我们可以发现测试方法调用三次show方法,分别传入了Animal、Dog、Cat对象,由于show()方法只判断了Dog是否是该对象,所以Dog返回了true,输出了Dog的信息,而其他的返回了false,则没有输出然后信息。
5、经典案例
Java的多态和转型都了解以后,现在趁热打铁,来点网上多态非常经典的例题:
来源:https://blog.csdn.net/Jian_Yun_Rui/article/details/52937791
public class A { public String show(D obj) { return ("A and D"); } public String show(A obj) { return ("A and A"); } } public class B extends A{ public String show(B obj){ return ("B and B"); } public String show(A obj){ return ("B and A"); } } public class C extends B{ } public class D extends B{ } public class Test { public static void main(String[] args) { A a1 = new A(); A a2 = new B(); B b = new B(); C c = new C(); D d = new D(); System.out.println("1--" + a1.show(b)); System.out.println("2--" + a1.show(c)); System.out.println("3--" + a1.show(d)); System.out.println("4--" + a2.show(b)); System.out.println("5--" + a2.show(c)); System.out.println("6--" + a2.show(d)); System.out.println("7--" + b.show(b)); System.out.println("8--" + b.show(c)); System.out.println("9--" + b.show(d)); } }
运行的结果:
前面3个强行发现还能得到答案,但是从第4个之后就有点头晕。
我们来慢慢分析第4个:首先是子类B类向上转型为父类A,而子类B有重写了父类A中的show(A obj)方法,所以a2变量能调用的只有父类A类中的show(D obj)和子类B中的show(A obj),而B是继承自A类的,D继承自B类,所以不可能调用show(D obj)方法,所以结果是4--B and A;剩下的依次类推。
当父类对象变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。如果子类中没有覆盖该方法,那么会去父类中寻找。但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
6、总结
通过上面对多态的学习,可以小结一下概念:
1、Java多态也可以用一句话表示:父类的引用指向子类的对象。
2、运行时多态的前提:继承,重写,向上转型。
3、多态能够减少重复代码,使代码变得简洁;提高了代码的扩展性。
4、多态其实是一种虚拟方法调用。归结为一句话:编译看左边,运行看右边。
5、向上转型就是是将子类对象转为父类对象。
6、继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
夯实Java基础(四)——面向对象之多态的更多相关文章
- 夯实Java基础系列1:Java面向对象三大特性(基础篇)
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...
- 夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!
目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接 ...
- 夯实Java基础系列目录
自进入大学以来,学习的编程语言从最初的C语言.C++,到后来的Java,. NET.而在学习编程语言的同时也逐渐决定了以后自己要学习的是哪一门语言(Java).到现在为止,学习Java语言也有很长一段 ...
- 夯实Java基础系列10:深入理解Java中的异常体系
目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调 ...
- Java工程师学习指南第1部分:夯实Java基础系列
点击关注上方"Java技术江湖",设为"置顶或星标",第一时间送达技术干货. 本文整理了微信公众号[Java技术江湖]发表和转载过的Java优质文章,想看到更多 ...
- Java基础-初识面向对象编程(Object-Oriented-Programming)
Java基础-初识面向对象编程(Object-Oriented-Programming) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Java是一门面向对象的程序设计语言.那么什 ...
- 夯实Java基础(十一)——内部类
1.内部类的概念 内部类顾名思义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类.对于很多Java初学者来说,内部类学起来真的是一头雾水,根本理解不清楚是个什么东西,包括我自己(我太菜 ...
- 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理
目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...
- 夯实Java基础系列5:Java文件和Java包结构
目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...
- 夯实Java基础系列7:一文读懂Java 代码块和执行顺序
目录 Java中的构造方法 构造方法简介 构造方法实例 例 1 例 2 Java中的几种构造方法详解 普通构造方法 默认构造方法 重载构造方法 java子类构造方法调用父类构造方法 Java中的代码块 ...
随机推荐
- 彻底弄懂UTF-8、Unicode、宽字符、locale
目录 Unicode.UCS UTF8 宽字符类型wchar_t locale 为什么需要宽字符类型 多字节字符串和宽字符串相互转换 最近使用到了wchar_t类型,所以准备详细探究下,没想到水还挺深 ...
- mysql的union和or
实践出真知! 使用union连接 select `id` from `表名` where 0=0 and active=1 and `fullname` like '王%' union select ...
- 抽丝剥茧分析asyncio事件调度的核心原理
先来看一下一个简单的例子 例1: async def foo(): print('enter foo ...') await bar() print('exit foo ...') async def ...
- JavaScript面试核心考点(精华)
引言 Javascript是前端面试的重点,本文重点梳理下 Javascript 中的常考基础知识点,然后就一些容易出现的题目进行解析.限于文章的篇幅,无法将知识点讲解的面面俱到,本文只罗列了一些重难 ...
- python的列表使用
1.什么是列表 列表是由一系列按特定顺序排列的元素,元素之间可以没有任何关系:可以创建空列表,也可以将任何东西添加进列表. 列表用 [ ] 表示: cars = ['golf', 'magotan', ...
- WINDOWS 安装ZeroMQ
zmq看起来很好用,但是安装起来不是一般麻烦.原来以为java绑定会提供jar包直接可使用,但是官网没有提供已经编译好的库文件和jar.多么的不方便啊!最终还是要自己动手编译! 安装java版本的zm ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(二十一):在小程序中使用 WebSocket (.NET Core)
本文将介绍如何在 .NET Core 环境下,借助 SignalR 在小程序内使用 WebSocket.关于 WebSocket 和 SignalR 的基础理论知识不在这里展开,已经有足够的参考资料, ...
- ServiceFabric极简文档-3. 发布脚本
web: Trap { Write-Host $_.Exception.Message; Continue }; Connect-ServiceFabricCluster Remove-Service ...
- infiniband install driver
硬件:Mellanox InfiniBand,主要包括 HCA(主机通道适配器)和交换机两部分 软件:CentOS 6.4 MLNX_OFED_LINUX-2.1-1.0.0-rhel6.4-x86_ ...
- 网页缓存相关的HTTP头部信息详解
前言 之前看完了李智慧老师著的<大型网站技术架构-核心原理与案例分析>这本书,书中多次提起浏览器缓存的话题,恰是这几天生产又遇到了一个与缓存的问题,发现自己书是没少看,正经走心的内容却不多 ...