Java三大特性之一——多态(详细版)

文章目录

  • 一、什么是多态
  • 二、重写
    • 2.1、重写的规则
  • 三、多态的实现条件
  • 四、向上转型
  • 五、向下转型
  • 六、动态绑定
  • 七、使用多态的优缺点
    • 7.1、优点
    • 7.2、缺点
  • 八、避免在构造方法中调用重写的方法

一、什么是多态

    Java多态是面向对象编程的一个重要特性,它允许不同的对象对同一消息做出不同的响应。具体点就是不同的对象去完成相同的一个任务完成后展现的结果不相同。比如:学生在学校里面学习,但是学生分为体育生和文化生,体育生在学校里面主要学习体能方面的训练技巧,文化生在学校里面主要学习文化知识,两者在学校里面都是进行学习这一件事,但是体育生和文化生学习得到的内容并不相同,体育生学习后的结果就是体能方面的提升,文化生学习后的结果就是将知识记忆在脑海中,两者都是进行学习,但是结果并不相同,这就是多态。
    体育生是一个对象,文化生是一个对象,两个对象完成学习这一件事情得到的结果不相同,这是一个多态现象的体现,但想要在程序世界实现多态就需要其他的特性的辅助了

二、重写

    重写,又称为“覆盖”,简单来说就是将原有的方法进行重新编写,但是方法名,返回类型,参数列表必须要相同,是子类对父类非静态,非private修饰,非final修饰,非构造方法等谁实现过程进行重新编写,即外壳不变,内部革新。

    当子类继承父类,并且子类调用从父类继承下来的方法时,该方法不适应当前对象的行为,又或者是行为类似但不尽相同,子类需要重新定义该行为,此时就可以将父类继承下来的方法进行重写,定义属于自己的特定的行为。 比如说学生类,学生对象在学校的行为就是学习,当体育生类继承学生类,体育生对象调用行为学习,但是该行为对于体育生来说不是很完整,体育生对象应该是学习体能训练技巧,此时我们就可以对行为学习进行重写,将其重新定义为学习体能训练技巧。

    对于已经投入使用的类,尽量不要轻易修改,避免出现不必要的麻烦。最好的方法就是重新定义一个类,来重复利用其中的共性内容,并且添加或者改动新的内容。

学生类

public class Student {protected String name;protected String id;protected int age;public Student(String name, String id, int age) {this.name = name;this.id = id;this.age = age;}public void study() {System.out.println("学生" + name + "在学习....");}
}

体育生类

public class CulStu extends Student{public CulStu(String name, String id, int age) {super(name, id, age);}}

    子类体育生对父类学生类中的行为学习进行重写

public class CulStu extends Student{public CulStu(String name, String id, int age) {super(name, id, age);}@Overridepublic void study() {//对父类的行为进行重写System.out.println("体育生" + name + "在学习体能训练技巧...");}
}

2.1、重写的规则

1、子类在重写父类方法时,必须与父类的原型一致:返回类型,参数列表,方法名要完全一致
2、被重写的方法返回类型可以不相同,但是必须具有父子类关系
3、访问权限不能比被重写的方法的访问权限低,就是重写后的方法的访问权限要大于等于被重写的方法的访问权限。例如:如果父类方法被public修饰,子类中重写方法的访问权限就不能是protected;如果被重写方法是被protected修饰,那么重写方法只能说public修饰或者是protected修饰。访问权限(从小到大0):private < 默认权限 < protected < public
4、被重写方法和重写后的方法,两者的方法签名是相同的。
5、父类中的构造方法、被static修饰的方法、被final修饰的方法,被private修饰的方法不可以被重写
6、重写的方法可以使用“@Override”注解来显示指定,该注解可以帮我们校验重写中出现的错误,比如:将重写方法名编写错了,父类中没有对于的方法名,那么此时编译会爆红,来提示我们重写出现错误。
7、只能在子类中进行重写父类的方法,被重写方法和重写方法必须构成父子类关系

在这里插入图片描述

    需要注意的是:被private修饰的方法不能够被重写的原因并非是访问权限的原因,重写后的方法的访问权限要大于等于被重写方法的访问权限,这也就意味着访问权限是可以相同的,这说明如果被重写方法的访问权限是private的话,重写后方法的访问权限也可以说private,但实际上这样编写代码还是会爆红,被private修饰的方法不能够被重写的原因实际上是因为private修饰符本身,被private修饰的方法是私有型方法,只允许在本类中调用,拥有父子类关系也不可以调用,而且重写后方法和被重写方法的方法签名是一样的,那说明这两者是同一个方法。因此被private修饰的方法是不可重写的

三、多态的实现条件

    在Java中要实现多态,必须要满足以下几点:
1、必须要含有父子类关系
2、子类中要重写父类的方法
3、通过父类的引用调用重写的方法

    多态的体现:在代码允许的时候,当传递不同的对象时,会调用对应类中的方法

体育生类

public class PEstu extends Student{public PEstu(String name, String id, int age) {super(name, id, age);}@Overridepublic void study() {System.out.println("体育生" + name + "在学习体能训练技巧...");}
}

文化生类

public class CulStu extends Student{public CulStu(String name, String id, int age) {super(name, id, age);}@Overridepublic void study() {//对父类的行为进行重写System.out.println("文化生" + name + "在学习文化知识...");}
}

学生类

public class Student {protected String name;protected String id;protected int age;public Student(String name, String id, int age) {this.name = name;this.id = id;this.age = age;}public void study() {System.out.println("学生" + name + "在学习....");}
}
public class Main {public static void func(Student student) {student.study();}public static void main(String[] args) {Student student1 = new CulStu("文化生","1111",18);func(student1);Student student2 = new PEstu("体育生","2222",18);func(student2);}
}

    当编译器在编译的时候func()方法并不知道会传递什么对象,也不知道会调用体育生类还是文化生类中的方法,只有等程序运行起来才知道会传递什么对象给方法,才知道会调用哪个类中的方法,需要注意的是:此时必须要使用父类的引用,func()方法的形参必须是父类类型的才可以。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9756285f38bf4e33a2af9c571c2218f1.png

    因为形参sdutens引用引用的对象不同,所以调用同一个study()方法时,所表现的形式不一样,我们把这个情况称之为多态。(不同的对象做同一件事情所表现的结果不同)

    第一次调用func()方法时,传递的是文化生类这个对象,studennt这个引用就指向文化生对象,此时student调用study()方法,就会调用子类中重写的syudy()方法,从而打印出”文化生 + name + 在学习文化知识…"

    第二次调用func()方法时,传递的是体育生类这个对象,studennt这个引用就指向体育生对象,此时student调用study()方法,就会调用子类中重写的syudy()方法,从而打印出”体育生 + name + 在学习文化知识…"

    在上述代码中,前三个类是由类的实现者编写的,最后一个类是类的调用者编写的。当类的调用者在编写func()这个方法的时候,参数类型是Student(父类),方法内部本身并不知道,也不关注将来会传递什么对象给student引用进行指向,此时studengt这个引用调用study()方法可能会有多种不同的表现,这种行为就称为多态。

四、向上转型

    在实例化对象时,我们一般是这样编写的:

Student studengt = new Student();

    但是我们看上面的代码会发现,并不是这样编写的,而是使用父类类型的引用来指向子类类型的对象

Student student1 = new CulStu("文化生","1111",18);

    这就是向上转型。
向上转型:创建一个子类对象,将其当做父类对象来使用

语法格式:   父类类型  对象名 = new  子类类型();

    父类类型的引用指向了子类对象,Student是父类,CulStu是子类,父类类型是可以引用子类对象的,我们知道继承本质上是is-a的关系,体育生是学生也是is-a的关系。

Student student1 = new CulStu("文化生","1111",18);文化生是学生
Student student1 = new CulStu("体育生生","1111",18);体育生是学生

    向上转型的使用场景有三个:
1、直接赋值
2、方法传递
3、方法返回

Student student1 = new CulStu("体育生生","1111",18);//1、直接传递
    public static void func(Student student) {//2、方法传递//将方法的参数类型设置为父类类型}
    public static Student func() {//3、方法返回return student;//将方法的返回值设置为父类类型}

    向上转型的优点:让代码变得更加灵活
    缺点:该引用无法调用子类的特有的方法(因为引用是父类类型的,引用本身就只能调用本类型的方法变量,无法调用子类的方法变量)

在这里插入图片描述

五、向下转型

    我们实现向上转型后,只能通过父类的引用调用父类的方法或者访问父类的变量,当我们需要调用子类的方法或者访问子类的变量时又该如何?重新new一个子类对象吗?不现实,如果重新new一个子类,那么和之前的子类对象就是两个对象,这两个对象之间是没有联系的。此时我们可以通过向下转型,将父类引用还原为子类引用,再通过该引用调用子类方法或访问变量即可。

    但需要注意的是,将父类引用还原为子类引用,是一个强制转换的过程,因为向上转型是符合is-a关系的,体育生类是学生类没有问题:父类引用 = 子类对象,但此时我们是将父类引用转换为子类对象,相当于是将父类引用赋予给子类对象,学生类是体育生类,这似乎不太符合常理吧,并非所有学生类都是体育生类,因此相当于是非is-a的关系而是a-is的关系,有点倒反天罡啊,因此此处需要强制类型转换

    public static void main(String[] args) {Student student = new CulStu("文化生","1111",18);CulStu culStu = (CulStu) student;//将父类引用强转为子类引用culStu.a = "嘻嘻";culStu.fun();}

    将父类引用强转为子类引用后,此时子类引用指向之前被父类引用指向的子类对象,也就是说现在是子类引用指向子类对象,即可通过子类引用访问子类变量,调用子类方法了。

    但是还有特别需要注意的:向上转型时父类引用指向体育生子类对象,当我们需要向下转换时,该父类引用只能强转为体育生子类引用,不可以将父类引用强转为文化生子类引用,否则会编译报错。

在这里插入图片描述

    此时父类引用指向文化生子类对象,但是却将父类引用强转为体育生子类引用,引用类型不对,结果编译就报错了,并没有语法上的错误。因此我们需要强转的时候就需要先判断此时的父类引用得到是指向哪个子类对象,我们需要用到instanceof关键字,该关键字可以判断引用是否指向该类的对象。

引用名 instanceof 类名;//判断引用是否指向该类对象,指向则返回true,否则放false

再配合上if语句即可判断并执行强转代码了。

    public static void main(String[] args) {Student student = new CulStu("文化生","1111",18);if(student instanceof CulStu) {//判断student引用是否指向Culstu类对象CulStu culStu = (CulStu) student;culStu.study();}if(student instanceof PEstu) {//判断student引用是否指向PEstu类对象PEstu pEstu = (PEstu) student;pEstu.study();}}

    一般向下转型使用比较少,因为涉及到强制类型转换,强转是不安全的,有可能会导致数据的丢失,这个是我们无法预知的,因此不到迫不得已不要出现强转的代码。

六、动态绑定

public class Main {public static void func(Student student) {student.study();}public static void main(String[] args) {Student student1 = new CulStu("文化生","1111",18);func(student1);Student student2 = new PEstu("体育生","2222",18);func(student2);}
}

    动态绑定又称之为运行时绑定。
大家可能会好奇,在func()方法中,明明是通过父类的引用去调用study()方法,但是最终显示的结果却是子类中重写的study()方法的结果,也就是说程序并非调用父类的study()方法,而是调用子类的study()方法

在这里插入图片描述

    通过反汇编查看汇编代码,确实是调用父类的study()方法,但是在运行的时候却调用了子类重写的study()方法,这个过程称之为运行时绑定,又称动态绑定。在代码运行的时候父类的study()方法被绑定到了子类中重写的study()方法中。

    动态绑定:又称为运行时绑定,在程序编译的时候不能确定方法的行为(结果),只有当程序运行的时候,才能够确定具体调用哪个类的方法。典型代表是方法重写,又称后期绑定

    静态绑定:在编译的时候编译器通过用户传递的实参类型就可以确定具体调用哪个方法。典型代表是方法重载,又称前期绑定

七、使用多态的优缺点

7.1、优点

    使用多态的好处:
1、可以降低圈复杂度

    圈复杂度是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙,那么就很容易理解,如果加上大量的if语句循环语句就会导致理解起来很复杂。因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称之为圈复杂度。当代码中没有条件语句和循环语句时,代码直接从头到尾一条路径执行,那么圈复杂度为1;如果代码中有一个if语句,那么此时代码出现了两种路径,那么圈复杂度为2。如果一个方法的圈复杂度太高,那么就需要考虑重构了
    而使用多态,通过运行时绑定能够避免使用if语句,还可以调用重写后有特殊要求的方法,从而降低代码的圈复杂度。

此时有以下代码:

class Shape {public void draw() {System.out.println("画图形");}
}
class Rect extends Shape {@Overridepublic void draw() {System.out.println("画菱形");}
}class Cycle extends Shape{@Overridepublic void draw() {System.out.println("画圆形");}
}
class Flower extends Shape {@Overridepublic void draw() {System.out.println("画一朵花");}
}
public class Main {}

现在我们需要打印两朵花,两个圆形,一个菱形:(我们可以用到数组)
1、不使用多态打印

public class Main {public static void main(String[] args) {Rect rect = new Rect();Cycle cycle = new Cycle();Flower flower = new Flower();String[] str = {"Flower","Flower","Cycle","Cycle","Rect"};for (String s:str) {if(s.equals("Flower")) {flower.draw();} else if (s.equals("Cycle")) {cycle.draw();} else {rect.draw();}}}
}

2、使用多态打印

    public static void main(String[] args) {Shape rect = new Rect();Shape cycle = new Cycle();Shape flower = new Flower();Shape[] shapes = {flower,flower,cycle,cycle,rect};for (Shape shape : shapes) {shape.draw();}}

    观察两个代码不难看出:不使用多态打印的代码大量使用条件语句和循环语句,那么伴随着圈复杂度提高,代码的可阅读性降低。

    2、可扩展能力强
如果此时需要增加打印,比如增加打印一个正方形,我们需要新定义一个正方形类,在不使用多态的情况下,需要在条件语句和循环语句上进行更改,需要做大量的修改操作,改动成本高如果是使用多态的方法打印,那么我们只需要在数组里面增加指向正方形类对象的父类引用即可,代码的改动较小,效率高。

    public static void main(String[] args) {Shape rect = new Rect();Shape cycle = new Cycle();Shape flower = new Flower();Shape[] shapes = {flower,flower,cycle,cycle,rect,square};for (Shape shape : shapes) {shape.draw();}}

7.2、缺点

    多态的缺点:代码的运行效率降低
    
1、属性没有多态性
    当父类和子类同时有同名属性的时候,通过父类引用,只能调用父类的成员属性
    
2、构造方法没有多态性

八、避免在构造方法中调用重写的方法

class A {public A() {func();}public void func() {System.out.println("A.func()");}
}class B extends A{private int num = 1;@Overridepublic void func() {System.out.println("B.func()" + num);}
}
public class Test {public static void main(String[] args) {A a = new B();}
}

    仔细观察这一段代码,会打印什么结果?

在这里插入图片描述

    首先调用子类的构造方法,在子类中的构造方法会先通过super( )调用父类的构造方法,但是在执行构造方法之前需要先执行构造代码块,调用父类的构造方法过程中再调用func()方法,此时满足动态绑定的条件:在继承体系下,子类中重写父类的方法,调用重写的方法。发生动态绑定调用子类中重写的func()方法,因为此时父类的构造方法还没执行完成,子类的构造代码块和构造方法就没有得以执行,因此子类中的成语变量处于未初始化状态,因此动态绑定调用子类重写的func()方法,方法里面打印子类未初始化的成员变量num,重写的func()方法执行完后父类的构造方法也执行完毕,接下来执行子类的构造代码块将num赋值为1,再执行子类的构造方法。因此程序最终打印结果为0。

在这里插入图片描述
用尽量简单的方式使对象进入可工作状态 用尽量简单的方式使对象进入可工作状态 用尽量简单的方式使对象进入可工作状态

    尽量不要在构造器(构造方法、构造代码块)中调用方法,如果这个方法被子类重写,构造器内就会触发动态绑定,从而调用子类中重写的构造方法,但是此时子类对象还没有构造完成,因此该过程就有可能会触发一些引用又极难发现的问题。因此在构造器中最好不调用方法,只对成员变量进行初始化即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/4489.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

连锁餐饮企业-凡塔斯,用千里聆RPA搭建用户评价管理系统,提升门店服务满意度

凡塔斯是大型连锁餐饮企业昊澜餐饮集团旗下餐饮品牌&#xff0c;是牛排自助餐头部品牌&#xff0c;旗下拥有凡塔斯、百分好、食物链KING自助烤肉及餐饮人才商学院等多个行业知名品牌。 创立至今&#xff0c;集团管理门店已发展到福建、广东、江西、浙江等十多个省市&#xff0c…

设备状态监控一定要直观,可视化大屏最适合这个工作

一、引言 在现代工业生产和各类设施运行中&#xff0c;设备的稳定运行至关重要。为了确保设备能够高效、可靠地工作&#xff0c;及时了解设备的状态是关键。而设备状态监控一定要直观&#xff0c;只有这样才能让操作人员和管理人员迅速掌握设备的运行情况&#xff0c;及时发现…

xxe靶机实战

靶机地址&#xff1a;https://www.vulnhub.com/entry/xxe-lab-1,254/ 下载好后解压 直接拖拽.ovf格式的文件到虚拟机里 打开kali扫描主机,靶机开着或者后台运行就行 arp-scan -I eth0 -l 扫描出来目标靶机ip地址192.168.142.145 nmap扫描端口 nmap -A -sS -T4 -P- --min-rat…

钉钉内集成第三方免密登录(Vue+.Net)

需要实现的效果就是在钉钉内点击应用能跳转到第三方网站并且免密登录 1.登录钉钉PC端管理后台 2.通过管理后台进去开发者后台 3.应用开发 创建H5微应用 4.应用创建成功后直接点权限管理全部授权 5.设置H5登录地址 6. 应用管理发布 至此需要配置的步骤全部已完成&#xff0c;…

画动态爱心(Python-matplotlib)

介绍 氵而已 由于用的是 AI&#xff0c;注释得非常清楚&#xff0c;自己改改也可以用 代码 # -*- coding: utf-8 -*- # Environment PyCharm # File_name 尝试1 |User Pfolg # 2024/11/05 22:45 import numpy as np import matplotlib.pyplot as plt import matplo…

理解 WordPress | 第五篇:页面构建器选择指南

WordPress 专题致力于从 0 到 1 搞懂、用熟这种可视化建站工具。 第一阶段主要是理解。 第二阶段开始实践个人博客、企业官网、独立站的建设。 如果感兴趣&#xff0c;点个关注吧&#xff0c;防止迷路。 什么是 WordPress 构建器 WordPress 构建器&#xff08;Page Builder&am…

硬件基础07 功率放大器

一、功放理论 在多级放大电路中&#xff0c;输出信号往往要送去驱动—定的装置。例如&#xff0c;这类装置包括收音机中扬声器的音圈、电动机的控制绕组等。多级放大电路除了应有电压放大级外&#xff0c;还要求有一个能输出一定信号功率的输出级。这类主要用于向负载提供功率的…

敬业签适配鸿蒙:开启多端协同新篇章

纯血鸿蒙&#xff0c;即华为推出的原生鸿蒙操作系统&#xff08;HarmonyOS Next&#xff09;&#xff0c;是一款面向全场景的分布式操作系统&#xff0c;它以其独特的微内核设计和多设备协同能力&#xff0c;引领着智能终端的新潮流。鸿蒙系统的推出&#xff0c;不仅标志着中国…

Matlab车牌识别课程设计报告模板(附源代码)

目 录 一&#xff0e;课程设计目的……………………………………………3 二&#xff0e;设计原理…………………………………………………3 三&#xff0e;详细设计步骤……………………………………………3 四. 设计结果及分析…………………………………………18 五. …

Apache HTTPD 换行解析漏洞(CVE-2017-15715)

Apache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞&#xff0c;在解析PHP时&#xff0c;1.php\x0A将被按照PHP后缀进行解析&#xff0c;导致绕过一些服务器的安全策略。 上传一个1.php&#xff0c;被拦截 在1.p…

用qrcode和pyzbar分别生成和解码二维码

我用的是anaconda环境&#xff0c;在anaconda命令行下&#xff0c;用pip分别安装以下库文件&#xff1a; pip install opencv-python pip install numpy pip install pillow pip install myqr pip install qrcode pip install zxing 生成二维码 打开pycharm&#xff0c…

软件测试学习笔记丨Vue学习笔记-基本介绍

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/23458 编译器使用&#xff1a;VScode 推荐插件 JavaScript (ES6) code snippets&#xff1a;包含 ES6 语法中的 JS 代码段Vetur&#xff1a;VSCode 支持 VUE 的工具Auto Close Tag&#xff…

【简历】25届江西某一本大学JAVA简历:不能把大厂的技能写到中厂上

注&#xff1a;为保证用户信息安全&#xff0c;姓名和学校等信息已经进行同层次变更&#xff0c;内容部分细节也进行了部分隐藏 校招的第一法则就是必须要确定校招层次。 开发岗分为大中小厂&#xff0c;不同的层次对学校背景、时间点、项目和考点的要求都不太一样&#xff0c…

微信订阅消息

一、订阅消息模板 进入微信小程序平台&#xff0c;开通订阅消息后进行模板申请 1.申请后得到模板id 2.直接在数据库中插入模板 二、创建订阅消息模板参数类 1.进入相关目录&#xff0c;创建订阅消息发送参数模板类&#xff0c;类属性根据模板的详细类容进行设置 2.在相应…

Vue3 + Element Plus简单使用案例及【eslint】报错处理

本电脑Vue环境已安装正常使用 博主使用npm 包管理器安装 Element Plus.有问题评论区帮忙指正,感谢阅读. 在完成的过程中如果遇到eslint报错 Parsing error &#xff1a;Unexpected token { eslint 这个报错&#xff0c;也可以尝试第7部分报错处理解决。 目录 1.新建项目 2…

MySQL索引、B+树相关知识总结

MySQL索引、B树相关知识汇总 一、有一个查询需求&#xff0c;MySQL中有两个表&#xff0c;一个表1000W数据&#xff0c;另一个表只有几千数据&#xff0c;要做一个关联查询&#xff0c;如何优化&#xff1f;1、为关联字段建立索引2、小表驱动大表 二、b树和b树的区别1、更高的查…

AI绘画凉了吗?都快2025年了你还没搭上AI这一便车吗?

在科技飞速发展的今天&#xff0c;AI 绘画如同一场绚丽的艺术风暴&#xff0c;席卷了整个创意领域。它以其独特的魅力和强大的功能&#xff0c;为艺术家、设计师以及普通爱好者们带来了前所未有的创作体验。 在数字化时代的浪潮下&#xff0c;人工智能(AI)技术正以前所未有的速…

常见 CSS 选择器用法

"Be Your Own Hero" CSS选择器是一种模式&#xff0c;用于选择需要应用CSS样式的HTML元素。以下是一些基本的CSS选择器类型&#xff1a; 1.标签选择器 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">&…

AIDD - 分子药物发现的计算方法现状总结

分子药物发现的计算方法现状总结 01 引言 药物发现的流程近年来因计算技术的飞速进步而发生了深刻变革。**计算辅助药物设计&#xff08;CADD, Computer-Aided Drug Design&#xff09;和人工智能驱动药物发现&#xff08;AIDD, Artificial Intelligence-Driven Drug Discover…

机器学习—代码中的推理

TensorFlow是实现深度学习算法的领先框架之一&#xff0c;另一个流行的工具是圆周率火炬&#xff0c;在这篇文章中&#xff0c;我们专注于张量流&#xff0c;那么如何在代码中实现推理&#xff1f; 让我们深入了解神经网络的一个非凡之处&#xff0c;同样的算法可以应用于这么…