夯实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中的代码块 ...
随机推荐
- 基于STM32之UART串口通信协议(一)详解
一.前言 1.简介 写的这篇博客,是为了简单讲解一下UART通信协议,以及UART能够实现的一些功能,还有有关使用STM32CubeMX来配置芯片的一些操作,在后面我会以我使用的STM32F429开发 ...
- HashMap源码分析(一):JDK源码分析系列
正文开始 注:JDK版本为1.8 HashMap1.8和1.8之前的源码差别很大 目录 简介 数据结构 类结构 属性 构造方法 增加 删除 修改 总结 1.HashMap简介 HashMap基于哈希表 ...
- RabbitMQ windows 安装后 此时不应有 \RabbitMQ。
在安装过程中,如果细心实际上在安装的细节显示starting Service后就已经提示这个问题,实际上就是没有注册到RabbitMQ这个服务. 在研究了很久之后才发现原来是安装路径的问题,原先是安装 ...
- 如何用ModelSim对Xilinx ISE产生的网表进行仿真
图: 在对设计的芯片进行测试时,经常要用到FPGA,可是里面的仿真工具却不如Modelsim那么好用,且在规模比较大时,ISE在仿真时,软件经常会报告内存限制的问题,此时一般会切换到Modelsim软 ...
- JSON.stringify() 的深入理解
目录 序言 语法 深入理解 序言 最近在看<你所不知道的javascript>[中卷]一书,第一部分是类型和语法.本文是基于这部分的产物.在强制类型转换->抽象值操作-> to ...
- Java用Zip进行压缩
这个总结源于Java编程思想第四版18.11节的案例: 完整代码地址: Java编程思想:压缩 相关Api地址: ZipStream ZipEntry ZipFile 进行压缩时: 1.创建Check ...
- VS2010中GetMenu()和GetSubMenu(0)为NULL引发异常的解决方法 及添加方法
对于前面问题的分析:来源于http://blog.163.com/yuyang_tech/blog/static/216050083201211144120401/ 解决方法1: //来源:http: ...
- 洛谷P2265 路边的水沟
题目 题目背景 LYQ市有一个巨大的水沟网络,可以近似看成一个n*m的矩形网格,网格的每个格点都安装了闸门,我们将从水沟网络右下角的闸门到左上角的闸门的一条路径称为水流. 题目描述 现给定水沟网的长和 ...
- 万字长文:ELK(V7)部署与架构分析
ELK(7版本)部署与架构分析 1.ELK的背景介绍与应用场景 在项目应用运行的过程中,往往会产生大量的日志,我们往往需要根据日志来定位分析我们的服务器项目运行情况与BUG产生位置.一般情况下直接在日 ...
- 第一届合天杯河北科技大学网络安全技术大赛 web6 writeup