初始Java篇(JavaSE基础语法)(6)(继承和多态)(下)

news2024/11/20 4:43:56

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程(ಥ_ಥ)-CSDN博客

所属专栏:JavaSE

                                                                         多态篇

目录

多态的概念

实现多态的条件 

必须在继承体系下实现向上转型:

子类必须对父类中的方法进行重写:

通过父类引用调用重写方法:

多态的优缺点:

避免在父类的构造方法中调用重写的方法


多态的概念

通俗来说,就是多种形态,具体点就是去完成某个行为时,当不同的对象去完成时会产生出不同 的状态。

例如:同样是吃早餐,这个人可能是吃包子,饺子;另外一个人却是吃面条。这就是不同的对象去完成同一件事情时所表现出来的状态不同。

实现多态的条件 

既然了解了什么是多态,接下来要知道什么情况下可以实现多态。

要想实现多态得满足以下三个条件:

1. 必须在继承体系下实现向上转型。

2. 子类必须对父类中的方法进行重写。

3. 通过父类引用调用重写方法。

下面就来解释这三个条件。

必须在继承体系下实现向上转型

就是指一个是子类,一个是父类,然后把子类对象给到父类的引用。 注意:这里的体系,说明不一定是要在直系继承关系下,可以是通过中间类间接继承。

例如:

向上转型:把子类对象给到父类的引用。

向下转型:将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的 方法,此时:将父类引用再还原为子类对象(强制类型转换)即可,即向下转换。简单理解就是把父类对象给到子类的引用。

                                                        结合该图理解 

向上转型的语法格式:父类类型  名称  =  new  子类类型();   向下转型的语法格式与其差不多。

向上(向下)转型的应用场景:直接赋值、作为方法的参数、作为方法的返回值。、

例如:

//向上转型
public class Test {
    public static void func1(Animal animal) {
        System.out.println("向上转型的场景之一:方法参数");
    }
    public static Animal func2() {
        System.out.println("向上转型的应用场景之一:方法的返回值");
        Dog dog = new Dog("大黄", 5);
        return dog;
        //return new Dog("大黄", 5);
        //上面两种写法都可以
    }
    public static void main(String[] args) {
        Animal animal = new Dog("大黄", 8);//直接赋值
        animal.eat();
        func1(new Dog("大黄", 5));//也可以先实例化一个对象,再传参
        func2();
    }
}

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。 

向下转型相较于向上转型而言很不安全,因为向下转型的基础是向上转型,再把父类对象给到子类引用,而子类可能不是只有一个,但是父类只有一个。

例如:狗类对象经过向上转型给到父类引用,再把这个父类引用当成对象给到猫类引用,这时就会报错。因为不安全,这也就是为什么向下转型不安全?因为不确定是原来的子类。

public class Test {
    public static void main(String[] args) {
        //向上转型
        Animal animal = new Dog("大黄", 5);
        //再把父类引用给到子类引用
        Cat cat = animal;
    }
}

这时就只能通过强制类型转换达到我们的目的。

 很遗憾的是:强制类型转换也是做不到的。 

那怎么样才能实现向下转型呢?很简单,通过同类型子类来转换。

例如:先把Dog类型进行向上转型给到animal引用,再把animal引用强制转换为Dog类型再向下转型给Dog引用。

子类必须对父类中的方法进行重写

首先,得了解什么是重写?

重写(override):也称为覆盖,是子类对父类中非静态、非private修饰、非final修饰、非构造的方法进行重新编写。重写之后的方法与原来的方法相比:在参数列表、方法名、返回值都要一致。即外壳不变,核心重写。

例如:

注意:

1. 当重写的方法返回值与原来的方法返回值构成父子(继承)关系时,这时返回值就可以不一样。这里的父子(继承)关系同样可以不是直系关系。

例如:

上面特殊情况这种在Java叫:协变类型。 

2. 子类中的重写方法的访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected。

3. 重写的方法, 可以使用 @Override 注解来显式指定。有了这个注解能帮我们进行一些合法性校验。例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法,就会编译报错,提示无法构成重写。

这个重写和我们前面在学习方法的重载时有点类似。下面就来比较两者之间的区别。

方法重写和方法重载的区别
区别处方法重写(override)方法重载(overload)
参数列表必须一样必须不一样
返回值除继承关系外,其余时都得一致未规定(可以一样,可以不一样)
适用范围存在继承关系的类中所有类
目的性实现多态性,子类可以根据自身需求对父类方法进行特定实现提供多个功能相似但参数不同的方法,方便调用者根据不同情况选择合适的方法

简单理解:方法重载是一个类的不同表现,而方法重写是子类与父类的一种不同表现。

【重写的设计原则】 对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容, 并且添加或者改动新的内容。

例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅 可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。 

第二点,我们刚刚也在代码中体会到了:何为子类必须对父类的方法进行重写。 

通过父类引用调用重写方法

class Animal  {
    public String name;
    public int age;
    public void eat() {
        System.out.println(this.name+" 正在吃放!");
    }
}

class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println(this.name+" 正在吃狗粮!");
    }
    public Dog(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog("大黄", 8);
        animal.eat();
    }
}

上面三步完成之后,就会发生动态绑定。 而动态绑定是多态的基础。

静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表方法重载。

动态绑定:也称为后期绑定(晚绑定、运行时绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。典型代表多态。

多态的优缺点:

1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else 。

什么叫 "圈复杂度" ? 圈复杂度是一种描述一段代码复杂程度的方式。一段代码如果平铺直叙, 那么就比较简单容易理解。而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂. 因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 "圈复杂度"。如果一个方法的圈复杂度太高, 就需要考虑重构。不同公司对于代码的圈复杂度的规范不一样,一般不会超过 10 。

例如:我们现在需要打印的不是一个形状了,而是多个形状。如果不基于多态,我们就只能通过if - else语句来进行打印。

//画一个圆
class Round {
    public void draw() {
        System.out.println("○");
    }
}

//画一个正方形
class Square {
    public void draw() {
        System.out.println("□");
    }
}

//画一个三角形
class Triangular {
    public void draw() {
        System.out.println("△");
    }
}


public class Test {
    public static void main(String[] args) {
        Round round = new Round();
        Square square = new Square();
        Triangular triangular = new Triangular();
        String[] shapes = {"round", "square", "triangular"};
        //开始判断需要画什么样的图形
        for (String shape:shapes) {
            if (shape.equals("round")) {
                round.draw();
            }else if (shape.equals("square")) {
                square.draw();;
            }else if (shape.equals("triangular")) {
                triangular.draw();
        }
    }
}

在Java中,判断两个字符串是否相等,应该使用 equals() 方法。这个方法比较的是字符串的内容,即两个字符串所包含的字符序列是否完全相同。这里有一个简单的示例:

//语法格式:字符串1.equals(字符串2); —— 得到的结果是布尔类型
public class Test {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = "world";

        boolean areEqual = str1.equals(str2); //使用equals()方法比较字符串内容
        System.out.println("str1 和 str2 是否相等? " + areEqual);

        areEqual = str1.equals(str3); //比较str1和str3
        System.out.println("str1 和 str3 是否相等? " + areEqual);
    }
}

注意:使用 == 操作符是比较两个字符串对象的引用是否相同,而不是比较它们的值是否相等。因此,通常情况下不推荐使用 == 来比较字符串内容。只有在确保两个字符串引用指向同一对象时(例如,它们都是同一个字符串字面量的引用),使用 == 才能得出正确的结果。

基于多态来打印图形。

//画一个图形的父类
class Shape {
    public void draw() {
        System.out.println("画一个图形!");
    }
}

//画一个圆
class Round extends Shape{
    //重写父类方法
    @Override
    public void draw() {
        System.out.println("○");
    }
}

//画一个正方形
class Square extends Shape{
    //重写父类方法
    @Override
    public void draw() {
        System.out.println("□");
    }
}

//画一个三角形
class Triangular extends Shape{
    //重写父类方法
    @Override
    public void draw() {
        System.out.println("△");
    }
}

public class Test {
    public static void main(String[] args) {
        //向上转型
        Shape[] shapes = {new Round(), new Square(), new Triangular()};
        for (Shape shape : shapes) {
            shape.draw();//通过父类引用调用重写方法
        }
    }
}

这里的shape.draw();就相当于shapes[i].draw。也就是通过父类引用调用重写方法。

2. 可扩展能力更强 如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低。

只需要新增一个类就可以了,而不使用多态的话,就得新增if-else语句来判断。

多态缺陷:代码的运行效率降低。

1. 属性没有多态性 当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性。

2. 构造方法没有多态性。

避免在父类的构造方法中调用重写的方法

class A {
    public A() {
        //在父类的构造方法中调用被重写的方法会发生动态绑定
        func();
    }
    public void func() {
        System.out.println("这是父类A的方法");
    }
}

class B extends A {
    @Override
    public void func() {
        System.out.println("这是子类B的方法");
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
    }
}

通过前面的学习,我们已经知道了:在实例化子类对象时,先帮助父类执行构造方法。

而在父类中的构造方法中,如果调用了被重写的方法,此时就会发生动态绑定。

 注意:如果此时子类重写的方法中存在成员变量,这个成员变量不会被初始化成我们想要的值,但是会有默认值。因为在调用子类的重写方法时,父类的构造方法都没有完成,自然其他的都还没有进行的。

可能有小伙伴有疑惑:哪里有通过父类引用调用被重写的方法?哪里有发生向上转型?

至于提到的“哪里有通过父类引用调用被重写的方法”,这通常不是直接在构造方法上下文中讨论的,而是更普遍地出现在多态的应用场景中。但构造方法内部调用非静态、非私有、非final的方法时,实际上也间接体现了这一点,因为尽管直接调用看似是父类方法,但由于动态绑定,最终执行的是子类的方法体。这里的关键是理解,即使调用点在父类构造器内,实际的对象身份(即内存中的对象)已经是子类的实例。

至于“哪里有发生向上转型”,向上转型是当你用父类的引用指向子类的对象时自然发生的现象。在构造方法调用的场景中,虽然没有直接的显式向上转型(比如 Parent p = new Child();),但实际上,当子类构造方法调用父类构造方法时,可以视为一种隐式的向上转型,因为父类构造器中的this引用在构造子类对象的上下文中代表了一个子类实例。在这个过程中,父类构造器内的代码通过这个this引用间接地操作了子类对象,而当它调用一个可被子类重写的方法时,就涉及到了动态绑定,这也是向上转型和动态绑定交互作用的一个体现。

其实暂时就只要知道不要在父类的构造方法中调用被重写的方法就可以了。随着我们学习的深入就会理解了。

好啦!本期初始Java篇(JavaSE基础语法)(6)(继承和多态)(下)的学习之旅就到此结束了!下一期我们再一起学习吧!

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

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

相关文章

Wireshark CLI | 过滤包含特定字符串的流

问题背景 源自于和朋友的一次技术讨论,关于 Wireshark 如何查找特定字符串所在的 TCP 流,原始问题如下: 仔细琢磨了下,基于我对 Wireshark 的使用经验,感觉一步到位实现比较困难,所以想着说用 Wireshark C…

Tomcat启动闪退怎么解决(文末附终极解决方案)

AI是这么告诉我的 Tomcat启动时出现闪退问题可能由多种原因引起,以下是解决此类问题的一些通用方法: 检查环境变量: 确保已经正确设置了JAVA_HOME和JRE_HOME环境变量,并指向正确的Java安装路径。将Java的bin目录添加到系统的PATH…

用户中心(下)

文章目录 计划登录逻辑接口简单说明cookie和session写代码流程后端逻辑层控制层测试用户管理接口 前端简化代码对接后端代理 计划 开发完成后端登录功能 (单机登录 > 后续改造为分布式 / 第三方登录)✔开发后端用户的管理接口 (用户的查询…

LLaMA详细解读

LLaMA 是目前为止,效果最好的开源 LLM 之一。精读 LLaMA 的论文及代码,可以很好的了解 LLM 的内部原理。本文对 LLaMA 论文进行了介绍,同时附上了关键部分的代码,并对代码做了注释。 摘要 LLaMA是一个系列模型,模型参…

u盘格式化后电脑读不出来怎么办?u盘格式化的东西还能恢复吗

随着科技的快速发展,U盘已成为我们日常生活和工作中不可或缺的数据存储工具。然而,有时我们可能会遇到U盘格式化后电脑无法读取的情况,或是误格式化导致重要数据丢失。面对这些问题,我们该如何应对?本文将为您详细解答…

python邮件发送

第一种方式 一:发送的邮件要设置授权码,通过邮箱邮箱授权码去验证,让邮件服务器帮我们去转发邮件到要接收的邮件,代码中的授权码,是需要登录126邮箱(我这里是以126邮件发送的,具体的以自己为准…

概念解析 | 互补学习系统

注1:本文系"概念解析"系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:互补学习系统(Complementary Learning Systems) 概念解析:互补学习系统 Paper Summary - “Complementary Learning Systems Theory Updated” | Rylan Schaeffer…

数据库MySQL的基本操作

在Linux里面,我们要对数据库MySQL进行操作时(例如修改MySQL的密码),不是直接在我们的终端上进行操作,而是通过终端连接进入到MySQL里面去,在进行操作,写SQL语句。 而安装C等的开发库sudo命令&a…

Crocoddyl 使用教程(二)

系列文章目录 前言 小车摆杆是另一个经典的控制实例。在这个系统中,一根欠驱动的杆子被固定在一辆一维驱动的小车顶部。游戏的目的是将杆子升到站立位置。 模型如下: https://en.wikipedia.org/wiki/Inverted_pendulum 我们用 表示小车质量、 表示摆杆质…

Visual studio调试技巧

Visual studio调试技巧 bug是什么?Debug和ReleaseDebugRelease 如何调试VS调试快捷键调试过程中查看程序信息查看临时变量的值查看内存信息查看调用堆栈查看汇编信息查看寄存器信息 编译常见错误编译型错误链接型错误运行时错误 bug是什么? bug的英文释…

机器学习笔记-22

终章 至此吴恩达老师的机器学习课程已经完成啦,总结一下: 1.监督学习的算法:线性回归、逻辑回归、神经网络和向量机 2.无监督学习的算法:K-Means、PCA、异常检测 3.推荐系统、大规模数据处理、正则化、如何评估算法 4.上限分析、…

Servlet_JSP

1.一些回顾 对于Tomcat部署中 我们有一些补充的点需要在此说明一下 1.如果我们想要查询MINEType的话 可以到TOMCAT_HOME/conf/web.xml中进行查询 里面记录了不同类型对应的MINEType 2.我们客户端发送请求数据给服务器之后 服务器会调用父类中的service方法 然后在内部决定调用…

用Jenkins Gerrit-Trigger插件实现提交gerrit后自动启动编译验证-解决编译依赖问题

用Jenkins Gerrit-Trigger插件实现提交gerrit后自动启动编译验证-CSDN博客讨论了如何利用插件在提交gerrit的时候自动出发一个jenkins job编译固件,但是没有解决编译依赖问题。本文提出一种解决方案 首先在git commit -m ""的时候在commit message中设置Depend-On:…

ControlNet官方资源链接【ControlNet论文原文】【持续更新中~】

ControlNet官方资源链接 ControlNet论文原文:https://arxiv.org/abs/2302.05543ControlNet官方GitHub:https://github.com/lllyasviel/ControlNetControlNet 1.1官方GitHub:https://github.com/lllyasviel/ControlNet-v1-1-nightlyControlNe…

深度学习之基于Vgg16卷积神经网络印度交警手势识别系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着智能交通系统的不断发展,手势识别技术在其中扮演着越来越重要的角色。特别是在印度等…

CVE-2017-11882分析和白象样本分析

CVE-2017-11882分析和白象样本分析 CVE-2017-11882是微软公布的一个远程代码执行漏洞,漏洞是由模块EQNEDT32.EXE公式编辑器引起,该模块在Office的安装过程中被默认安装,该模块以OLE技术(Object Linking and Embedding&#xff0c…

《网络安全---frida应用实践---某付费视频应用一举拿下》

文章目录 目标应用环境:步骤1、查壳2、定位付费界面布局3、找到可疑方法4、那就看下请求信息吧,看下有没有思路5、其他请求(列表,视频信息,获取播放url)6、请求参数加密算法7、图片信息解密8、数据请求关键点9、以上都是废话10、直接找关键hook点总结相关源码1、文章仅供…

2.初探MPI——点对点通信(阻塞)

系列文章目录 初探MPI——MPI简介初探MPI——(阻塞)点对点通信初探MPI——(非阻塞)点对点通信初探MPI——集体通信 文章目录 系列文章目录前言一、Sending & Receiving message1.1 简介1.2 发送消息1.3 接收消息1.4 MPI 发送…

AI智能名片商城小程序构建企业级私域的IMC模型:IP、MarTech与Content的深度融合

在数字化营销的新时代,为企业定制开发的AI智能名片B2B2C商城小程序,结合我们丰富的私域运营实践,我们深刻领悟到构建企业级私域的三大核心要素:IP(企业人设)、MarTech(营销技术)和Co…

【自动化测试】使用MeterSphere进行接口测试

一、接口介绍二、接口测试的过程三、接口自动化测试执行自动化流程 四、接口之间的协议HTTP协议 五、 接口测试用例设计接口文档 六、使用MeterSphere创建接口测试创建接口定义设计接口测试用例 一、接口介绍 自动化测试按对象分为:单元测试、接口测试、UI测试等。…