【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活

💬 欢迎讨论:如对文章内容有疑问或见解,欢迎在评论区留言,我需要您的帮助!

👍 点赞、收藏与分享:如果这篇文章对您有所帮助,请不吝点赞、收藏或分享,谢谢您的支持!

🚀 传播技术之美:期待您将这篇文章推荐给更多对需要学习Java语言、低代码开发感兴趣的朋友,让我们共同学习、成长!

1. 什么是多态?

1.1 什么是多态?

举一个简单的例子

小滑是一个比较狡诈的人,小刚是一个性格比较值的人,李华比较喜欢交朋友,当李华与小滑交朋友的时候就需要谨慎,当李华和小刚交朋友的时候需要柔和。

在这里插入图片描述
同样是交朋友,李华却需要表现出两种状态。

换言之,多态,它允许同一个行为在不同的对象上有不同的表现形式

多态的定义

多态(Polymorphism)是面向对象编程的一个核心特性,它允许同一个行为(方法调用)在不同的对象上有不同的表现形式。简单来说,多态使得程序可以以统一的方式调用不同类型的对象,从而提高了代码的灵活性和可扩展性。

示例:

class Animal{public void eat(){System.out.println("吃饭~");}}class Dog extends Animal{// 重写父类方法public void eat(){System.out.println("吃骨头");}}public class Cat extends Animal{public void eat(){System.out.println("吃鱼");}public static void main(String[] args){// 引用类型都为 AnimalAnimal cat = new Cat();Animal dog = new Dog();//都调用 eat方法cat.eat();dog.eat();}
}

在这里插入图片描述

1.2 实现多态需要满足的条件

需要满足的提条件

  1. 继承关系 (最基本的条件)
  2. 子类重写父类的方法
  3. 通过父类对象的引用取调用重写的方法

符合上述的三个部分就会发生动态的绑定,而动态的绑定是多态的基础

2. 向上转型

2.1 向上转型的本质及原理

向上转型(Upcasting)的本质是 子类对象可以被赋值给父类引用,也就是说,将一个子类对象看作是它的父类类型。这种机制基于面向对象的 继承关系 和 IS-A(是一个)原则,即子类对象是父类对象的一种特殊形式。

向上转型的原理

继承关系子类继承父类,因此子类对象自然包含父类中定义的所有方法和属性。
在向上转型时,父类引用只会访问父类中声明的方法和属性,而不会直接访问子类的扩展方法和属性。

运行时多态:尽管父类引用只能看到父类的接口,但调用方法时,具体执行的是子类的重写方法。这就是 运行时多态 的体现。

示例:

class Animal{public String name;public String color;protected int age;//构造器public Animal(String name,String color, int age){this.name = name;this.color = color;this.age = age;}public void eat(){System.out.println(name + "吃饭~");}public void sleep(){System.out.println(name + "睡觉~");}}public class Cat extends Animal{// 构造器public Cat(String name, String color, int age){super(name,color,age);}public void eat(){System.out.println(name + "吃鱼");}public void mimi() {System.out.println("喵喵~~");}public static void main(String[] args){Animal cat = new Cat("小花","白色",2);// 调用子类重写的cat.eat();// 调用子类特殊的;会报错cat.mimi();}}

一般来说,只有数据类型一样的变量才能赋值,为什么这两个变量也能用等号呢?

因为他们是继承关系

2.2 使用场景及说明

  1. 直接赋值:将子类对象直接赋值给父类类型的引用。
  2. 方法传参:在方法调用时,将子类对象作为父类类型的参数传递。
  3. 方法返回:方法返回一个父类类型的对象,但实际返回的是子类对象。

示例

package cn.nyist.animal;class Animal{public String name;public String color;protected int age;//构造器public Animal(String name,String color, int age){this.name = name;this.color = color;this.age = age;}public void eat(){System.out.println(name + "吃饭~");}public void sleep(){System.out.println(name + "睡觉~");}}public class Cat extends Animal{// 构造器public Cat(String name, String color, int age){super(name,color,age);}public void eat(){System.out.println(name + "吃鱼");}public void mimi() {System.out.println("喵喵~~");}public static void F(Animal a){a.eat();}public static Animal fAnimal(){return new Cat("小灰","灰色",4);}public static void main(String[] args){// 直接赋值Cat cat1 = new Cat("小白","白色",3);Animal animal1 = cat1;animal1.eat();// 直接赋值Animal animal2 = new Cat("小花","花色",2);animal2.eat();// 方法传参Cat cat2 = new Cat("小黑","黑色",3);F(cat2);// 方法返回Animal animal4 = fAnimal(); animal4.eat();}}

说明

  1. Cat 类型的对象 cat1 被赋值给 Animal 类型的引用 animal1,这是 向上转型。
    animal1 虽然是 Animal 类型,但实际指向的是 Cat 对象,因此调用 animal1.eat() 时,通过多态机制,执行的是 Cat 类中重写的 eat() 方法。

    使用 new Cat(…) 创建了 Cat 对象,并将其赋值给 Animal 类型的引用 animal2。
    和前面的场景一样,通过多态机制,调用的是 Cat 类中重写的 eat() 方法。

  2. F 方法的参数是 Animal 类型,因此当调用 F(cat2) 时,Cat 类型的对象 cat2 被 向上转型 为 Animal 类型。
    在方法内部,a.eat() 调用的是实际对象 cat2 的 eat() 方法,通过多态机制,执行 Cat 类中的重写方法。

  3. 方法 fAnimal 的返回类型是 Animal,但方法内部实际上返回了一个 Cat 对象。
    当返回值被赋值给 Animal 类型的引用 animal4 时,发生 向上转型。
    调用 animal4.eat() 时,通过多态机制,调用了 Cat 类中重写的 eat() 方法。

向上转型的优点:让代码实现更简单灵活
向上转型的缺陷:不能调用到子类特有的方法

3. 静态绑定和动态绑定

3.1 什么是静态绑定?什么是动态绑定?

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

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

用Java 字节码来帮助我们理解
Java 字节码中有两种常见的方法调用指令:invokespecial 和 invokevirtual。这两种方法调用指令可以帮助我们区分 静态绑定 和 动态绑定。下面逐一解释。

写一个继承类型的java程序:

package cn.nyist.animal;class Animal{public String name;public String color;protected int age;//构造器public Animal(String name,String color, int age){this.name = name;this.color = color;this.age = age;}public void eat(){System.out.println(name + "吃饭~");}public void sleep(){System.out.println(name + "睡觉~");}}public class Cat extends Animal{// 构造器public Cat(String name, String color, int age){super(name,color,age);}public void eat(){System.out.println(name + "吃鱼");}public void mimi() {System.out.println("喵喵~~");}public static void F(Animal a){a.eat();}public static Animal fAnimal(){return new Cat("小灰","灰色",4);}public static void main(String[] args){// 直接赋值Cat cat1 = new Cat("小白","白色",3);Animal animal1 = cat1;animal1.eat();// 直接赋值Animal animal2 = new Cat("小花","花色",2);animal2.eat();// 方法传参Cat cat2 = new Cat("小黑","黑色",3);F(cat2);// 方法返回Animal animal4 = fAnimal();animal4.eat();}}

先用 javac 编译 .java 文件,生成 .class 文件。
然后使用 javap 针对 .class 文件反编译,不加 .java 后缀

javac Cat.javajavap -c Cat

在这里插入图片描述
运行后显示就是Java字节码

然后我们需要找到main方法中的字节码
在这里插入图片描述
invokespecial 指令绑定的是父类方法、私有方法或构造方法,这些在编译时已经明确目标,因此属于 静态绑定(如下)。

9: invokespecial #39 // Method "<init>":(Ljava/lang/String;Ljava/lang/String;I)V
28: invokespecial #39 // Method "<init>":(Ljava/lang/String;Ljava/lang/String;I)V
45: invokespecial #39 // Method "<init>":(Ljava/lang/String;Ljava/lang/String;I)V

invokevirtual 指令绑定的是普通实例方法(如 eat()),这些方法会在运行时根据实际对象类型决定调用的目标,因此属于 动态绑定(如下)。

16: invokevirtual #31 // Method cn/nyist/animal/Animal.eat:()V
33: invokevirtual #31 // Method cn/nyist/animal/Animal.eat:()V
62: invokevirtual #31 // Method cn/nyist/animal/Animal.eat:()V

3.2 toString动态绑定

创建 Dog 对象并打印

Dog dog = new Dog(name: "小黑", age: 5);
System.out.println(dog);

这里创建了一个 Dog 类型的对象,并通过 System.out.println(dog) 输出。

System.out.println() 方法内部接受的是一个 Object 类型的参数(也就是 dog 发生了向上转型,变成了 Object 类型)。

System.out.println() 的核心方法定义如下:

public void println(@Nullable Object x) {String s = String.valueOf(x); // 将对象转为字符串synchronized (this) {print(s);    // 输出字符串newLine();   // 换行}
}

调用 println(Object x) 方法时,dog 被向上转型为 Object 类型,作为参数传递进去。

然后,String.valueOf(x) 将对象 x 转为字符串。

String.valueOf(Object obj) 是一个静态方法,其代码如下:

public static String valueOf(Object obj) {return (obj == null) ? "null" : obj.toString();
}

这里会检查对象 obj 是否为 null:
如果是 null,返回字符串 “null”。
如果不是 null,调用对象的 toString() 方法,将对象转为字符串。

调用 toString() 方法

如果 Dog 类没有重写 toString() 方法,则默认会调用 Object 类的 toString() 方法。
Object.toString() 的默认实现是:

public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

默认输出为 类的全限定名 + @ + 对象的哈希值,比如 Dog@1a2b3c。

4. 向下转型

4.1 概念

向下转型(Downcasting) 是指将 父类的引用 转换为 子类的引用。这通常发生在需要调用子类特有的方法或属性时。

向下转型需要开发者明确知道父类引用所指向的实际对象是哪个子类,因为只有当实际对象是目标子类类型时,向下转型才是安全的

在这里插入图片描述

 public class TestAnimal {public static void main(String[] args) {Cat cat = new Cat("小黑",2);Dog dog = new Dog("大黄", 1);// 向上转型Animal animal = cat;animal.eat();animal = dog; // 注意,animal 引用的是Dog类型animal.eat();// 向下转型// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastExceptioncat = (Cat)animal;cat.mew();// animal本来指向的就是狗,因此将animal还原为狗也是安全的   dog = (Dog)animal;dog.bark();}}

4.2 正确使用

向下转型前提是对象实际类型必须匹配:向下转型的对象实际类型必须是目标类型,否则会抛出 ClassCastException。

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了instanceof,如果该表达式为true,则可以安全转换

使用 instanceof 检查:

if (animal instanceof Dog) {Dog dog = (Dog) animal;dog.fetch();
} else {System.out.println("无法转换为 Dog 类型");
}

示例

import java.util.ArrayList;class Animal {void sound() {System.out.println("动物发出声音");}
}class Dog extends Animal {void fetch() {System.out.println("狗在刨土");}
}class Cat extends Animal {void climb() {System.out.println("猫在爬树");}
}public class DowncastingExample {public static void main(String[] args) {ArrayList<Animal> animals = new ArrayList<>();animals.add(new Dog());animals.add(new Cat());for (Animal animal : animals) {if (animal instanceof Dog) {Dog dog = (Dog) animal;dog.fetch(); // 输出:狗在刨土} else if (animal instanceof Cat) {Cat cat = (Cat) animal;cat.climb(); // 输出:猫在爬树}}}
}

5. 小练

一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func

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

执行结果 D.func() 0

解析:

  • 构造 D 对象的同时, 会调用 B 的构造方法.
  • B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
  • 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.
  • 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.

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

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

相关文章

JAVA-Exploit编写(7)--http-request库文件上传使用续篇

目录 1.http-request简介 2. 依赖导入 3.靶场实战 3.1 简单使用 3.2 增加判断机制 3.3 文件上传成功后的利用 3.4 正则匹配上传文件的返回的路径 1.http-request简介 http-request 是一个库 里面提供很多方法&#xff0c;使得很容易就可以构造http请求,相比于之前使用的…

抖音小程序一键获取手机号

前端代码组件 <button v-if"!isFromOrderList"class"get-phone-btn" open-type"getPhoneNumber"getphonenumber"onGetPhoneNumber">一键获取</button>// 获取手机号回调onGetPhoneNumber(e) {var that this tt.login({f…

NavVis手持激光扫描帮助舍弗勒快速打造“数字孪生”工厂-沪敖3D

在全球拥有近100家工厂的舍弗勒&#xff0c;从2016年开启数字化运营进程&#xff0c;而当前制造、库存、劳动力和物流的数字化&#xff0c;已无法支持其进一步简化工作流程&#xff0c;亟需数字化物理制造环境&#xff0c;打造“数字孪生”工厂。 NavVis为其提供NavVis VLX 3…

2024年博客之星主题创作|Android 开发:前沿技术、跨领域融合与就业技能展望

目录 引言 一、推动 Android 应用创新的核心力量 1.1 人工智能与机器学习的崛起 1.2 增强现实&#xff08;AR&#xff09;与虚拟现实&#xff08;VR&#xff09;的应用扩展 1.3 5G技术的推动 1.4 跨平台开发技术的成熟 1.4.1 React Native 1.4.2 Flutter 1.4.3 Taro …

[Qt] Box Model | 控件样式 | 实现log_in界面

目录 1、样式属性 &#xff08;1&#xff09;盒模型&#xff08;Box Model&#xff09; 2、控件样式示例 &#xff08;1&#xff09;按钮 &#xff08;2&#xff09;复选框 &#xff08;3&#xff09;单选框 &#xff08;4&#xff09;输入框 &#xff08;5&#xff09…

Golang Gin系列-6:Gin 高级路由及URL参数

在本章中&#xff0c;我们将深入研究使用Gin框架的高级路由和URL参数。我们将介绍如何创建和使用路由组、应用中间件、提取路径参数、处理查询字符串、处理静态文件以及使用HTML模板。 路由分组 为什么要使用路由组&#xff1f; 使用路由组有助于保持代码结构整洁有序。当路由…

【2025】拥抱未来 砥砺前行

2024是怎样的一年 2024在历史画卷上是波澜壮阔的一年&#xff0c;人工智能的浪潮来临&#xff0c;涌现出无数国产大模型。 22年11月ChatGPT发布&#xff0c;它的出现如同在平静湖面上投下一颗巨石&#xff0c;激起了层层波澜&#xff0c;短短五天用户数就达到了100万&#xff0…

FreeRTOS系统移植

前言 学习RTOS之前最重要的就是要学会将系统移植到单片机中&#xff0c;这里可以直接使用cubemx生成移植好的工程&#xff0c;也可以下载库来进行自己移植&#xff0c;这里我选择下载库来自己移植&#xff0c;因为这样可以配合Linux开发stm32单片机程序。 一、下载系统代码 …

MySQL 事务及MVCC机制详解

目录 什么是事务 事务的隔离级别 数据库并发的三种场景 读-写 什么是事务 事务就是一组DML语句组成&#xff0c;这些语句在逻辑上存在相关性&#xff0c;这一组DML语句要么全部成功&#xff0c;要么全部失败&#xff0c;是一个整体。MySQL提供一种机制&#xff0c;保证我们…

微服务学习-快速搭建

1. 速通版 1.1. git clone 拉取项目代码&#xff0c;导入 idea 中 git clone icoolkj-microservices-code: 致力于搭建微服务架构平台 1.2. git checkout v1.0.1版本 链接地址&#xff1a;icoolkj-microservices-code 标签 - Gitee.com 2. 项目服务结构 3. 实现重点步骤 …

arkime安装

这次试一下新的办法 先下载centOS 7 然后改成阿里云镜像 输入命令备份官方yum源配置文件 cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak 下载阿里云源配置&#xff0c;覆盖原文件 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirr…

/usr/bin/ssh-copy-id: ERROR: no identities found 解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

基于springboot+thymeleaf+Redis仿知乎网站问答项目源码

项目介绍 基于springbootthymeleafRedis仿知乎网站问答项目源码&#xff0c;可以作为毕业设计项目参考学习 按照需要一定动手能力 发文章&#xff0c;发视频&#xff0c;发想法&#xff0c;提问回答&#xff0c;注册登录 开发环境 使用技术&#xff1a;springbootthymeleafRe…

apisix的authz-casbin

目录 1、apisix的auth-casbin官方介绍 2、casbin介绍和使用 2.1基本知识&#xff1a; 2.2使用例子 3、配置插件 4、postman调用 5、auth-casbin的坑 1、apisix的auth-casbin官方介绍 authz-casbin | Apache APISIX -- Cloud-Native API Gateway 2、casbin介绍和使用 c…

自动驾驶之DriveMM: All-in-One Large Multimodal Model for Autonomous Driving

1. 写在前面 工作之后,主要从事于偏工程比较多的内容, 很少有机会读论文了,但2025年,由于之前有些算法的背景, 后面可能会接触一些多模态大模型相关的工作,所以又调头有点往算法的方向偏移, 而算法呢,很重要的一点就是阅读论文。2025年,再拾起论文这块的工作。 今天…

BGP分解实验·9——路由聚合与条件性通告(1)

路由聚合是有效控制缩减BGP路由表的方法之一&#xff0c;路由聚合的前提和IGP一样&#xff0c;需要有路由目标存在BGP表中&#xff0c;与IGP不同的是&#xff0c;BGP路由聚合可以定义按需抑制路由的能力。 实验拓扑如下所示&#xff1a; 现在开始把从R1的R5的基础配置先准备好…

Linux C\C++方式下的文件I/O编程

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客 《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 Lin…

C语言:位段

位段的内存分配: 1. 位段的成员可以是 int unsigned int signed int 或者是char &#xff08;属于整形家族&#xff09;类型 2. 位段的空间上是按照需要以4个字节&#xff08; 类型 int &#xff09;或者1个字节&#xff08; char &#xff09;的方式来开辟的。 3. 位段涉及…

C++实现矩阵Matrix类 实现基本运算

本系列文章致力于实现“手搓有限元&#xff0c;干翻Ansys的目标”&#xff0c;基本框架为前端显示使用QT实现交互&#xff0c;后端计算采用Visual Studio C。 目录 Matrix类 1、public function 1.1、构造函数与析构函数 1.2、获取矩阵数值 1.3、设置矩阵 1.4、矩阵转置…

HTML<form>标签

例子 具有两个输入字段和一个提交按钮的HTML表单&#xff1a; <form action"/action_page.php" method"get"> <label for"fname">First name:</label> <input type"text" id"fname" name"f…