Java多态的全面系统解析
作者:最爱吃鱼罐头 发布时间:2023-06-22 13:39:22
茫茫人海千千万万,感谢这一秒你看到这里。希望我的能对你的有所帮助!共勉!
愿你在未来的日子,保持热爱,奔赴山海!
Java基础知识(多态)
多态
因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
多态的定义和存在的必要条件
多态的定义:
多态是指同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作。
就举动物类的例子吧,cat和dog都是属于动物这一类,而动物呢,都有一个共同的行为就是吃😋吧,而不同的动物所吃的食物都大不相同吧!
猫呢,它喜欢吃鱼
!
而对于狗呢,它就比较喜欢啃骨头
!
所以多态就是对于吃这一行为来说,每种动物对吃这一行为所表现的行为都不尽相同。
多态存在的三个必要条件
继承或者实现
在多态中必须存在有继承或者实现关系的子类和父类。
方法的重写
子类对父类中某些方法进行重新定义(重写),在调用这些方法时就会调用子类的方法。
基类引用指向派生类对象,即父类引用指向子类对象
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
✍多态的格式:
父类类型 变量名 = new 子类类型();
变量名.方法名();
多态格式可以充分体现了同一个接口,使用不同的实例而执行不同操作。
接下来我们具体来进行案例体会体会吧!👇👇👇
多态的案例
多态我们首先要知道的一点:
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。如果子类没有重写该方法,就会调用父类的该方法。
总结起来就是:编译看左边👈,运行看右边。👉
首先我们先定义一个父类动物类,动物有吃的行为!
接着定义一个猫类和狗类去继承动物类,重写里面的吃行为!
☘️具体代码如下:
定义动物父类:
package com.nz.pojo;
/**
* 先定义一个父类 --> 动物类
* 动物都有一个吃的行为属性
*/
public class Animal {
public void eat() {
System.out.println("动物它们都会吃东西!!!");
}
}
定义猫咪子类:
package com.nz.pojo;
/**
* 定义猫类继承动物类,
* 随之重写里面的吃行为,因为猫也有吃的行为,但是猫喜欢吃罐头
*/
public class Cat extends Animal{
public void eat() {
System.out.println("小喵咪都喜欢吃鱼罐头!");
}
}
定义小狗子类:
package com.nz.pojo;
/**
* 定义狗类继承动物类,
* 随之重写里面的吃行为,因为狗也有吃的行为,但是狗喜欢啃骨头
*/
public class Dog extends Animal{
public void eat() {
System.out.println("小狗狗都爱啃骨头!");
}
}
定义测试类,测试多态的形式:
package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;
/**
* 测试多态的形式
*/
public class Demo {
public static void main(String[] args) {
// 多态形式,创建猫类对象
Animal animal = new Cat();
// 调用的是Cat的 eat
animal.eat();
// 多态形式,创建狗类对象
Animal animal2 = new Dog();
// 调用的是Dog的eat
animal2.eat();
}
}
得到的结果:
小喵咪都喜欢吃鱼罐头!
小狗狗都爱啃骨头!
类的大致结构:
可以看出我们可以使用多态的属性得到不同的动物的一个吃的行为属性!
多态的好处
提高了代码的拓展性,使用父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用。
具体我们来看看吧:
继续使用上述的动物类、猫类、狗类吧。
定义测试类:
package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;
/**
* 测试多态的好处
*/
public class Demo2 {
public static void main(String[] args) {
// 创建猫和狗对象
Cat cat = new Cat();
Dog dog = new Dog();
// 调用catEat
catEat(cat);
// 调用dogEat
dogEat(dog);
/*
多态的好处:
以上各个动物的吃的方法, 我们都可以使用animalEat(Animal a)方法来代替。
并且执行效果都一样, 所以我们可以使用animalEat直接替代了不同动物的吃方法。
*/
animalEat(cat);
animalEat(dog);
}
/*
定义几个不同吃的方法,看看具体调用后的结果是什么吧!
*/
public static void catEat (Cat cat){
cat.eat();
}
public static void dogEat (Dog dog){
dog.eat();
}
public static void animalEat (Animal animal){
animal.eat();
}
}
执行结果:
小喵咪都喜欢吃鱼罐头!
小狗狗都爱啃骨头!
小喵咪都喜欢吃鱼罐头!
小狗狗都爱啃骨头!
可以看出,由于多态的特性,我们的animalEat()方法传入的Animal类型参数,并且它是我们的Cat和Dog的父类类型,父类类型接收子类对象,所以我们可以将Cat对象和Dog对象,传递给animalEat()方法。
所以我们可以完全使用animalEat()方法来替代catEat()方法和dogEat()方法,达到同样的效果!以至于我们可以不必再单独写xxxEat()方法来传入指定的动物参数了,从而实现了实现类的自动切换。
所以多态的好处体现在:可以使我们的程序编写的更简单,并有良好的扩展性。
多态的弊端
从上面的多态的好处,可以看到我们可以使用父类的参数代替了某个子类的参数,从而达到程序的扩展!
但是对于某个子类有些独有的功能方法时,此时我们的多态的写法就无法访问子类独有功能了。
具体来瞧瞧?
代码如下:
重新定义下猫的子类:
package com.nz.pojo;
/**
* 定义猫类继承动物类,
* 随之重写里面的吃行为,因为猫也有吃的行为,但是猫喜欢吃罐头
*/
public class Cat extends Animal{
public void eat() {
System.out.println("小喵咪都喜欢吃鱼罐头!");
}
/**
* 增加一哥猫咪特有的玩球方法()
*/
public void playBall() {
System.out.println("小喵咪都喜欢小球!");
}
}
定义测试类:
package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
/**
* 测试多态的弊端!
*/
public class Demo3 {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat();
animal.playBall();//编译报错,编译看左边,Animal没有这个方法
}
}
可以看到动物类和猫类有个共同的eat吃方法,但是呢,猫咪多了个玩球球的方法。而对于动物对象来说,它本身动物类没有玩球球的方法,所以它的编译就直接没有通过了!
那有什么方法解决呢?且看下一章节吧!
引用类型转换
1. 引用类型转换是什么,为什么需要它?
从上面的多态的弊端的案例中,我们可以看到,我们使用动物对象时无法直接访问到猫类中的玩球球方法,这也就是我们之前说的编译看左边,运行看右边。
而在我们使用多态方式调用方法时,首先检查会左边的父类中是否有该方法,如果没有,则编译错误。也就代表着,父类无法调用子类独有的方法。、
所以说,如果编译都错误,更别说运行了。这也是多态给我们带来的一点小困扰,而我们如果想要调用子类特有的方法,必须做向下转型。
2. 向上转型(自动转换)
对于向下转型,我们先来讲解下向上转型的概念吧。
向上转型:
多态本身是子类向父类向上转换(自动转换)的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。
对于父类和子类的关系来说,具体来看图说话:
父类相对与子类来说是大范围的类型,Animal是动物类,是父类。而Cat是猫咪类,是子类。
所以对于父类Animal来说,它的范围是比较大的,它包含一切动物,包括猫咪类和小狗类。
所以对于子类类型这种范围小的,我们可以直接自动转型给父类类型的变量。
使用格式:
父类类型 变量名 = new 子类类型();
如:Animal animal = new Cat();
相当于有:
Animal animal = (Animal) new Cat();
相当于自动帮我们了一个隐形的转换为动物类的一个过程,因为动物本身就包含了猫咪。
3. 向下转型(强制转换)
向上转型可以知道它是子类自动转换为父类的一个过程,所以我们现在再来看看向下转型的定义:
向下转型:
向下转型就是由父类向子类向下转换的过程,这个过程是强制的。一个需要将父类对象转为子类对象,可以使用强制类型转换的格式,这便是向下转型。
为什么这种就必须自己强制加上一个类型转换过程呢?
对于父类和子类的关系来说,我们接着看图说话:
对于猫咪类的话,它在动物类中只是其中的一部分吧,而对于动物类来说,它有许多其他子类动物如狗,牛,猪等等。
所以对于动物父类想要向下转型的时候, 它此时不知道指向那个子类,因为不确定呀,所以就必须自己加上强制的类型转换的一个过程。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:
Animal animal = new Cat();
Cat cat = (Cat) animal;
cat.playBall();// 此时我们就可以使用猫咪的特有方法啦
所以对于多态的弊端,无法使用子类特有的参数,我们也解决啦,可以通过向下转型的方法,从而将类型强制转换为某个子类对象后,再去调用子类的特有方法!
4. 向下转型的问题
虽然我们可以使用向下转型使得我们可以使用子类的独有方法,但是转型的过程中,一不小心就会遇到这样的问题了,来,我们来看看下面的代码:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了 ClassCastException
,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。
5. 转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
定义狗类中额外的独有遛狗方法:
package com.nz.pojo;
/**
* 定义狗类继承动物类,
* 随之重写里面的吃行为,因为狗也有吃的行为,但是狗喜欢啃骨头
*/
public class Dog extends Animal{
public void eat() {
System.out.println("小狗狗都爱啃骨头!");
}
public void walk() {
System.out.println("小狗在被我溜着!");
}
}
定义测试类
package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;
/**
* 测试多态的向下转型的问题
*/
public class Demo4 {
public static void main(String[] args) {
// 向上转型的过程
Animal animal = new Cat();
// 调用了猫咪的吃方法
animal.eat();
// 向下转型
Dog dog = (Dog) animal;
dog.walk(); // 调用的是 Dog 的 walk 可以通过,但是会运行报错
}
}
得到结果:
小喵咪都喜欢吃鱼罐头!
Exception in thread "main" java.lang.ClassCastException: com.nz.pojo.Cat cannot be cast to com.nz.pojo.Dog
at com.nz.Demo4.main(Demo4.java:20)
我们可以看到,虽然我们的代码通过编译,但是终究在运行时,还是出错了,抛出了 ClassCastException
类型转换的异常。
其实我们可以知道,我们在上面的时候,创建了Cat类型对象,而在向下转型时,将其强行转换为了Dog类型,所以程序在运行时,就会抛出类型转换的异常!
那我们如何可以避免这种异常发生呢?且看下一节分析!
6. instanceof关键字
Java为我们提供一个关键字instanceof
,它可以帮助我们避免了ClassCastException
类型转换异常的发生。
那如何做呢?
格式:
变量名 instanceof 数据类型
解释:
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。
代码实现:
package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;
/**
* 使用instanceof解决类型转换异常!
*/
public class Demo5 {
public static void main(String[] args) {
// 向上转型的过程
Animal animal = new Cat();
// 调用了猫咪的吃方法
animal.eat();
// 向下转型
if (animal instanceof Cat){
Cat cat = (Cat) animal;
cat.playBall(); // 调用的是 Cat 的 playBall
} else if (animal instanceof Dog){
Dog dog = (Dog) animal;
dog.walk(); // 调用的是 Dog 的 walk
}
}
}
结果:
小喵咪都喜欢吃鱼罐头!
小喵咪都喜欢小球!
可以发现,它可以帮助我们在做类型转换前,判断该类型是否属于该类型或者子类类型,如果是,我们就可以强转啦!
来源:https://blog.csdn.net/Saturn_Mentos/article/details/122661609


猜你喜欢
- Compose中我们应该怎么使用地图呢?像之前我们在xml里面创建MapView,都是在Activity里面,管理MapView生命周期,和
- 如何创建可以存放各种类型的数组?根据JavaSE的语法知识储备,如果现在让你们创建如标题一样的数组,你会怎么创建呢?答案是:使用 Objec
- 请求网络数据是在安卓开发中使用最频繁的一个功能,网络请求的体验决定了用户对整个APP的感觉,因此合理地使用缓存对网络请求的数据进行处理极为重
- RestTemplate加@Autowired注入不了1、在启动类加入如图箭头所示代码:然后在进行@Autowired发现不报错了。完美解决
- 本文实例为大家分享了C#实现飞行棋小游戏的具体代码,供大家参考,具体内容如下逻辑图 以下是掷色子的一个代码,比较有代表性,里面的逻
- Java栈之链式栈存储结构实现一、链栈采用单链表来保存栈中所有元素,这种链式结构的栈称为链栈。二、栈的链式存储结构实现package com
- 一、简介虚拟函数从C#的程序编译的角度来看,它和其它一般的函数有什么区别呢?一般函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行
- 自动去除图像扫描黑边/// <summary>
- ProgressBar进度条,分为旋转进度条和水平进度条,进度条的样式根据需要自定义,之前一直不明白进度条如何在实际项目中使用,网上演示进度
- 使用Javamail发送邮件,必需的jar包(请下载javamail的源文件,官方下载页:http://www.oracle.com/tec
- 系统的基本架构 我们假设一个系统System包含Service客户服务中心、Shop网上购物中心和Office网上办公中心三个独立的网站。
- 导言目前截屏的方法很多,root不适用,要么其他方法就是有局限性,而其中官方给出的方案最好—MediaProjection介绍Android
- 由于对运算符重载不是多么理解诶,于是就百度了一下,结果发现一个解释很有趣的百度知道,分享看看。回答:+-*/这样的运算符重定义,比如你自定义
- 关键字 static1. 概述static 是一种修饰符static 是Java中表静态的关键字它可以修饰成员变量、成员方法、代码块被sta
- 这篇文章主要介绍了springmvc处理模型数据ModelAndView过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一
- 对过滤器doFilter里chain.doFilter()函数的理解关于chain.doFilter()函数在最近的使用中不是很理解,但是考
- 现阶段,我们创建了最简单的Android项目,现在在此公布github链接https://github.com/neuyu/android-
- 前言相信大家应该都有所体会,在以前我们要实现流动性布局,比较繁琐,Google开源了一个项目叫FlexboxLayout,相信大家都不陌生。
- 动态表单的含义是不要手动定义,直接在配置文件中进行定义。1.手动进行定义<form-beans > <form-bean
- 解释:二叉树的深度:从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。二叉树的宽度:二叉树的每一层中