当前位置: 首页 > news >正文

【java面向对象进阶】------多态

1. 初识多态


1.1 什么是多态

多态(Polymorphism) 是面向对象编程中的一个重要特性,指的是“同一操作作用于不同的对象,可以有不同的解释和执行结果”。

更具体地说:

多态 = 同类型的对象,表现出的不同形态。

例如:一个 Person 类型的引用,它可以指向 StudentTeacherAdministrator 对象,虽然都是 Person 类型,但调用 show() 方法时会根据实际对象类型显示不同的信息。

这体现了“同一个方法调用,因对象不同而行为不同”的特点。


1.2 多态的应用场景与表现形式

1.2.1应用场景

在实际开发中,多态常用于以下场景:

  • 统一接口处理多种子类对象:如注册系统中,学生、老师、管理员都可以通过同一个注册方法完成注册。

在这里插入图片描述

在这里插入图片描述

  • 提高代码扩展性:新增一种用户类型(如家长),无需修改原有注册逻辑,只需继承父类并重写方法即可。
  • 实现解耦:上层代码只依赖抽象(父类/接口),不关心具体实现细节。

1.2.2表现形式

多态最典型的表现形式是:

父类类型 对象名称 = new 子类对象();

比如:

Person p = new Student(); // 父类引用指向子类对象

此时:

  • p 的编译类型是 Person
  • p 的运行时类型是 Student

当调用 p.show() 时,实际执行的是 Student 类中的 show() 方法,这就是动态方法绑定(运行时决定)


1.3 多态使用的前提条件

要实现多态,必须满足以下三个条件:

前提条件说明
有继承关系子类必须继承父类(或实现接口)
有父类引用指向子类对象Fu f = new Zi(); 这种写法
有方法的重写(Override)子类必须重写了父类的方法,否则无法体现“不同行为”

⚠️ 注意:如果子类没有重写父类方法,则调用的是父类方法,不会出现多态现象。


1.4 多态实例分析

我们来看以下 Java 示例代码:

1.4.1 Person 类(父类)

public class Person {private String name;private int age;//构造方法:public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}//getter和setter方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}//show 方法public void show() {System.out.println(name + "," + age);}
}
package duo_tai;import java.security.cert.CertPathValidatorSpi;public class Student extends Person {@Overridepublic void show() {System.out.println("学生的信息为:" + getName() + ", " + getAge());}
}

1.4.2 Student、Teacher、Administrator 类(子类)

它们都继承自 Person,并重写了 show() 方法

public class Student extends Person {@Overridepublic void show() {System.out.println("学生的信息为:" + getName() + ", " + getAge());}
}
public class Teacher extends Person {@Overridepublic void show() {System.out.println("老师的信息为:" + getName() + ", " + getAge());}
}
public class Administrator extends Person {@Overridepublic void show() {System.out.println("管理员的信息为:" + getName() + ", " + getAge());}
}

1.4.3 Test 测试类

package duo_tai;public class Test {public static void main(String[] args) {//创建三个对象,并调用 register方法Student s =new Student();s.setName("张三");s.setAge(18);Teacher t= new Teacher();t.setName("王建国");t.setAge(30);Administrator admin = new Administrator();admin.setName("管理员");admin.setAge(35);register(s);register(t);register(admin);}// 此方法能接收 老师,学生 和 管理员// 参数类型为Person,即上述三者的父类public static void register(Person p){p.show();}
}

输出结果:

在这里插入图片描述

1.4.4 解析:

  • register(Person p) 接收任何 Person 的子类对象
  • 调用 p.show() 时,JVM 会在运行时判断 p 实际是什么类型,然后调用对应的 show() 方法
  • 这就是动态绑定(Dynamic Binding),也是多态的核心机制

2. 多态中调用成员的特点


2.1 调用成员的特点

在多态的场景下(即:父类引用指向子类对象),当我们通过父类引用去访问成员时,会表现出不同的行为规则。

2.1.1总结一句话:

变量调用:编译看左边,运行也看左边
方法调用:编译看左边,运行看右边


2.1.2 变量调用:编译看左边,运行也看左边

  • 编译期:JVM 根据引用类型(左边)判断是否有该变量。
  • 运行期:实际获取的是父类中的变量值,不会“动态”取子类的变量。
代码:
Animal a = new Dog();
System.out.println(a.name); // 输出:动物
分析:
类型name 字段
Animalname = "动物"
Dogname = "狗"

虽然 a 指向的是 Dog 对象,但 aAnimal 类型的引用,所以:

  • 编译时:检查 Animal 是否有 name 字段 → 有 → 编译成功
  • 运行时:取的是 Animal 类中的 name 值 → "动物"

即使子类有自己的同名变量,也不会被覆盖或替换,因为成员变量不支持重写,只支持隐藏(Shadowing)。

⚠️ 注意:这不是“重写”,而是“隐藏”。子类可以定义和父类同名的变量,但这只是局部遮蔽,并不影响父类变量的访问。


2.1.3方法调用:编译看左边,运行看右边

  • 编译期:根据引用类型(左边)判断是否有该方法。
  • 运行期:根据实际对象类型(右边)决定执行哪个方法。
代码:
Animal a = new Dog();
a.show(); // 输出:Dog --- show方法
分析:
  • Animal 中有 show() 方法 → 编译通过
  • 实际对象是 Dog,且 Dog 重写了 show() 方法 → 运行时执行 Dog 的版本

这就是动态方法绑定(Dynamic Method Binding),也是多态的核心体现。


2.2实际案例:

2.2.1 完整代码:

package duo_tai;public class Test01 {public static void main(String[] args) {Animal a = new Dog();System.out.println(a.name); // 输出:动物a.show();                   // 输出:Dog --- show方法}
}class Animal {String name = "动物";public void show() {System.out.println("Animal --- show方法");}
}class Dog extends Animal {String name = "狗"; // 隐藏了父类的 name@Overridepublic void show() {System.out.println("Dog --- show方法");}
}class Cat extends Animal {String name = "猫";@Overridepublic void show() {System.out.println("Cat --- show方法");}
}

2.2.2 输出结果:

在这里插入图片描述

2.3 内存图解

1 程序启动,加载 Test.class 到方法区,准备执行 main 方法。

在这里插入图片描述

2 main 方法被压入栈内存,开始执行。

在这里插入图片描述

3 JVM 加载 Animal.class 到方法区,初始化父类结构。

在这里插入图片描述

4 JVM 加载 Dog.class 到方法区,初始化子类结构(包含继承自 Animal 的成员)。

在这里插入图片描述

5 栈中创建引用变量 a,(类型为 Animal

在这里插入图片描述

6 执行 Animal a = new Dog();:在堆中创建 Dog 对象,包含父类和子类各自的 name 字段及方法信息;栈中的引用变量 a(类型为 Animal)指向堆中的 Dog 对象。

在这里插入图片描述

7 成员变量查找路径

当执行 a.name 时:

  • JVM 查找:Animal 类中有没有 name?→ 有 → 返回 Animal.name
  • 不会去查 Dogname,即使它存在!

因为成员变量是静态绑定(static binding),由引用类型决定。

在这里插入图片描述
在这里插入图片描述

8 成员方法查找路径

当执行 a.show() 时:

  • 编译检查:Animal 类中有 show() 吗?→ 有 → 编译通过
  • 运行时:JVM 查看对象的实际类型(Dog),找到其虚方法表(Vtable),调用 Dog.show()

方法是动态绑定(dynamic binding),由对象类型决定。

在这里插入图片描述
在这里插入图片描述


2.4 补充知识:变量隐藏 vs 方法重写

比较项成员变量成员方法
是否支持重写❌ 不支持✅ 支持
是否支持隐藏✅ 支持(Shadowing)❌ 不支持(但可覆盖)
绑定时机静态绑定(编译期)动态绑定(运行期)
访问依据引用类型实际对象类型

示例:Animal a = new Dog();

  • a.name → 获取 Animal.name
  • a.show() → 执行 Dog.show()

3. 多态的优势与弊端


3.1 多态的优势

多态是面向对象编程中极具价值的特性,其主要优势体现在以下几个方面:

3.1.1 提高代码的扩展性与复用性

在方法定义中使用父类类型作为参数,可以接收所有子类对象。例如:

public void work(Animal a) {a.eat();
}

该方法可传入 DogCat 等任意子类对象,无需为每个子类单独写方法,极大提升了代码的通用性和复用性。

3.1.2 实现解耦合,便于维护和扩展

多态使得程序上层逻辑(如调用方法)只依赖于父类或接口,不关心具体实现细节。当新增子类时,无需修改原有代码,只需继承并重写方法即可。

💡 示例:

Person p = new Student(); // 可以随时换成 Teacher 或 Administrator
p.work(); // 后续业务变化不影响调用逻辑

这种“面向抽象编程”的思想,让系统更灵活、易于维护。


3.2 多态的弊端

尽管多态带来了诸多好处,但也存在一个明显的局限性:

3.2.1 不能直接调用子类的特有功能

由于引用是父类类型,编译器只能识别父类中存在的成员。如果子类有父类没有的特有方法(如 lookHome()catchMouse()),则无法通过父类引用直接调用。

错误示例:
Animal0 a = new Dog0();
a.lookHome(); // 编译错误!Animal 中没有 lookHome 方法

⚠️ 原因:编译看左边 —— 编译器检查的是 Animal0 类中是否有此方法,没有就报错。


3.3 回顾引用数据类型的类型转换

Java 中引用类型的类型转换分为两种方式:

类型转换方式说明
自动类型转换(向上转型)子类对象赋值给父类引用,无需强制转换,安全且自动完成。
例如:Animal a = new Dog();
强制类型转换(向下转型)将父类引用转回子类类型,必须显式转换,可能存在风险。
例如:Dog d = (Dog)a;

注意:只有当实际对象确实是目标子类类型时,才能成功转换,否则会抛出 ClassCastException


3.4 强制类型转换解决问题

为了调用子类特有的功能,必须将父类引用强制转换为子类类型

3.4.1 解决方案:使用 instanceof 判断后再转换

if (a instanceof Dog0) {Dog0 d = (Dog0) a;d.lookHome(); // 正确调用子类特有方法
} else if (a instanceof Cat0) {Cat0 c = (Cat0) a;c.catchMouse();
} else {System.out.println("没有这个类型,无法转换");
}

优点:避免运行时异常,增强程序健壮性。

3.4.2 JDK 14 新特性:模式匹配简化判断与转换

Java 14 引入了 instanceof 的模式匹配语法,更加简洁:

//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if (a instanceof Dog0 d) { // 自动转换并赋值给 dd.lookHome();
} else if (a instanceof Cat0 c) {c.catchMouse();
} else {System.out.println("没有这个类型,无法转换");
}

优势:一行代码完成判断 + 转换 + 变量声明,减少冗余代码。


3.5 实际案例

3.5.1代码:

package duo_tai;public class Test02 {public static void main(String[] args) {Animal0 a = new Dog0();a.eat(); // 输出:狗吃骨头(运行时调用 Dog 的 eat)// 无法直接调用子类特有方法// a.lookHome(); // 编译错误!// 使用 instanceof + 强制转换解决if (a instanceof Dog0 d) {d.lookHome(); // 输出:狗看家} else if (a instanceof Cat0 c) {c.catchMouse();} else {System.out.println("没有这个类型,无法转换");}}
}class Animal0 {public void eat() {System.out.println("动物在吃东西");}
}class Dog0 extends Animal0 {@Overridepublic void eat() {System.out.println("狗吃骨头");}public void lookHome() {System.out.println("狗看家");}
}class Cat0 extends Animal0 {@Overridepublic void eat() {System.out.println("猫吃小鱼干");}public void catchMouse() {System.out.println("猫抓老鼠");}
}

3.5.2 输出结果:

在这里插入图片描述

http://www.dtcms.com/a/508265.html

相关文章:

  • 湖南常德广宇建设网站个人开个装修小公司
  • SSAS-如何通过Visual Studio直连SSAS
  • SAIL-VL2本地部署教程:2B/8B参数媲美大规模模型,为轻量级设备量身打造的多模态大脑
  • 卯兔科技网站建设云数据库可以做网站吗
  • wap网站建设兴田德润实惠网站开发外包合同范本
  • h5游戏免费下载:危险货车
  • 设置ubuntu系统时间为北京时间
  • TiDB和MySQL的不兼容点
  • Unity中rb.MovePosition的误区和相关物理系统知识详解
  • 基于W5500芯片实现DHCP自动获取IP功能
  • 了解学习Python3编程之面向对象
  • html5 特效网站网站制作沈阳
  • 网站公司怎么做今天军事新闻最新消息详细
  • MathJax本地化显示数学符号
  • CGroups资源控制实战【Linux】
  • 【数学】诱导公式
  • TDengine 数学函数 PI 用户手册
  • 郑州企业网站类似于凡科的网站
  • maven简介与安装
  • 网站通栏广告设计会员制网站建设
  • 【DIY】RS232串口监听头制作
  • 【CMakeLists.txt】project(LibreCAD VERSION 2.2.2)
  • 网站怎么加链接网店美工实训报告
  • Marin说PCB之SI----做信号完整性仿真时需要注意的地方--01
  • 用C语言实现组合模式
  • 石家庄哪里有网站推广深圳网站建设开发哪家好
  • C#开发学习杂笔(更新中)
  • 【JAVA】实体类注解不持久化至数据库
  • 【Java 开发工程师面试场景题大全】
  • 站长推荐跳转福州网站制作维护服务