- 第 159 篇 -
Date: 2026 - 02- 04
Author: 郑龙浩(仟墨)
本篇笔记创作时间:2025-12-28
复习时突然发现忘记发布这篇了
【Java基础】多态
文章目录
- 【Java基础】多态
-
- [1. 多态是基本了解](#1. 多态是基本了解)
- [2. 多态的好处 | 问题(缺点)](#2. 多态的好处 | 问题(缺点))
-
- [2.1 多态的好处](#2.1 多态的好处)
- [2.2 多态的问题(缺点)](#2.2 多态的问题(缺点))
- [3 多态下的类型转换](#3 多态下的类型转换)
-
- [3.1 前提](#3.1 前提)
- [3.2 自动类型转换 | 强制类型转换](#3.2 自动类型转换 | 强制类型转换)
- [3.3 `instanceof`关键字 | 检查强制转换是否正确](#3.3
instanceof关键字 | 检查强制转换是否正确) - [3.4 实际应用 | 代码示例](#3.4 实际应用 | 代码示例)
- [4 案例:](#4 案例:)
1. 多态是基本了解
多态是在继承/实现情况下的一种现象, 表现为:对象多态、行为多态
多态的核心规则:
- 方法调用 :编译 时检查「父类」方法签名,运行时执行「子类」重写的方法
- 成员变量访问 :编译 时和运行时都访问「父类」中的成员变量
多态的实现条件:
-
继承关系 :存在
extends或implements关系 -
向上转型 :父类引用指向子类对象
java// 举例 ParentClass reference = new ChildClass(); // 这种就是父类引用子类对象 -
方法重写 :必须要有子类重写父类中的方法
用下面的代码解释:
Test.java
java
package zhenglonghao.polymorphsm1;
public class Test {
public static void main(String[] args) {
// 多态(方法、成员):
// 方法 - 编译时看左边,运行时看右边:编译时检查父类方法,运行时调用子类方法
// 成员变量 - 编译时看左边,运行时也看左边:编译和运行时都使用父类的(对象.成员变量 访问的是父类中的成员,而不是子类中的成员)
Animal animal1 = new Tortoise(); // 乌龟
animal1.run(); // 实际调用「子类Tortoise」的run()方法(不是父类Animal的run()方法)
System.out.println(animal1.name); // 输出父类Animal的name
Animal animal2 = new Wolf(); // 狼
animal2.run(); // 实际调用「子类Wolf」的run()方法(不是父类Animal的run()方法)
System.out.println(animal2.name); // 输出父类Animal的name
/* 输出结果:
乌龟跑的慢
Animal
狼跑的快
Animal
*/
}
}
Animal.java
java
package zhenglonghao.polymorphsm1;
public class Animal {
String name = "Animal";
public void run(){
System.out.println("动物跑");
}
}
Tortoise.java
java
package zhenglonghao.polymorphsm1;
public class Tortoise extends Animal{
String name = "Tortoise";
@Override
public void run(){
System.out.println("乌龟跑的慢");
}
// 乌龟游泳 - 乌龟独有功能,多态下不能使用该方法
public void swim(){
System.out.println("乌龟会游泳");
}
}
Wolf.java
java
package zhenglonghao.polymorphsm1;
public class Wolf extends Animal{
String name = "Wolf";
@Override
public void run(){
System.out.println("狼跑的快");
}
// 狼吃羊 - 狼独有功能,多态下不能使用该方法
public void eatSheep(){
System.out.println("狼吃羊");
}
}
2. 多态的好处 | 问题(缺点)
2.1 多态的好处
-
右边对象解耦(可以任意更换子类对象)
什么意思呢?
就是右边的子类对象,可以任意更换(前提是满足多态的条件)
可以换乌龟,狼,或者其他动物
javaAnimal tortoise = new Tortoise() Animal wolf = new Wolf(); -
父类对象做某方法的参数时,可以传入任意子类对象(如下方法
printAnimalRunSpeed()中参数虽然是父类对象,但是可以传入子类对象,且该方法中用的也是子类方法)
代码示例好处
java
package zhenglonghao.polymorphsm1;
public class Test2 {
public static void main(String[] args) {
/* 多态的好处:
1. 右边对象解耦(可以任意更换子类对象)
2. 父类对象做参数时,可以传入任意子类对象(如下方法printAnimalRunSpeed()中参数虽然是父类对象,但是可以传入子类对象,且该方法中用的也是子类方法)
*/
Animal tortoise = new Tortoise();
Animal wolf = new Wolf();
// 因为多态,可以用同一个方法名,传入不同的对象
printAnimalRunSpeed(tortoise); // 打印的是乌龟的速度
printAnimalRunSpeed(wolf); // 输出的是狼的速度
}
// 方法作用:可以将任意动物对象送给这个方法,并打印不同动物的跑步速度
// 因为多态,所以虽然是父类对象,但是实际调用的是子类对象中的方法
// 虽然参数是父类对象,但是可以将子类对象传入,且调用的方法依然是子类对象的 方法
public static void printAnimalRunSpeed(Animal animal) {
animal.run(); // 虽然是父类的方法,但是实际调用的是子类的方法
}
}
2.2 多态的问题(缺点)
多态下不能使用子类的独有功能
比如下面的代码,多态状态下,想调用Tortoise / Wolf的独有方法(不是方法重写的方法),是无法调用的
java
package zhenglonghao.polymorphsm1;
public class Test3 {
// 多态状态下,想调用`Tortoise / Wolf`的独有方法(不是方法重写的方法),是无法调用的
public static void main(String[] args) {
Animal tortoise = new Tortoise();
Animal wolf = new Wolf();
printAnimalSpecialFunction(tortoise);
printAnimalSpecialFunction(wolf);
}
// 打印动物独有功能
public static void printAnimalSpecialFunction(Animal animal) {
// animal.eatSheep(); // 报错 因为这个是狼独有的功能,没有进行「方法重写」所以无法调用
// nimal.swim(); // 报错 因为这个是乌龟独有的功能,没有进行「方法重写」所以无法调用
}
}
3 多态下的类型转换
3.1 前提
多态下没法直接调用子类的独有功能(没有「方法重写」的方法)
如何解决这个问题,就需要「多态下的「类型转换」」
3.2 自动类型转换 | 强制类型转换
如果是子类转父类,会自动转;如果是父类转子类,必须强制转换
- 向上转型(子转父类) :自动类型转换:
父类 变量名 = new 子类(); - 向下转型(父转子类) :强制类型转换:
子类 变量名 = (子类)父类变量;
java
// 1. 向上转型 - 自动转换
Animal animal = new Tortoise(); // 子类转父类,自动完成
// 2. 向下转型 - 强制转换
Tortoise tortoise = (Tortoise) animal; // 父类转子类,需强制转换
3.3 instanceof关键字 | 检查强制转换是否正确
在强制转化的时候,一般是父类转子类 才能强转成功,如果不小心写成了子类转子类 ,编译时不出错,但是运行的时候会报错 ,为了避免这种情况的出现,需要提前使用instanceof判断该对象是否可以「强制转换」
instanceof的作用:判断某个对象的真实类型
比如Animal animal = new Tortoise(); // 子类转父类,自动完成的类型明面上是Animal,但是真实类型是Tortoise
用法1(传统用法):
java
// 如果 对象 naimal 无法转换成 Tortoise 类型, 会返回false (比如naimal是Wolf类型时,是子类转子类,就会返回false)
// 如果 对象 naimal 可以转换成 Tortoise 类型,会返回true (比如naimal是Naimal类型时,是父类转子类,就会返回true)
// 一句话说就是:判断animal的「真实类型」是否为 Tortoise
if (animal instanceof Tortoise) {
...
// 只有可以将naimal强制转换为Tortoise类型,才可以执行当前代码
}
用法2(新的用法):
Java 14引入的语法特性,可以直接在if条件中完成类型检查和转换,且会转换为一个新的对象,无需在if语句内进行强制类型转换
使用方法:在 instanceof 后面直接声明变量名
- 如果类型匹配,变量会自动被赋值且无需手动转换
- 变量作用域仅限于 if 语句块内
- 方法2更加简洁
java
if (animal instanceof Tortoise) {
Tortoise tortoise = (Tortoise) animal; // 需要手动强制转换(冗余)
tortoise.swim();
}
// 新的用法
if (animal instanceof Tortoise tortoise) {
tortoise.swim(); // 直接使用,自动完成转换
}
3.4 实际应用 | 代码示例
解决 2.2 问题中的:多态下不能使用子类的独有功能的问题
java
package zhenglonghao.polymorphsm1;
// Date: 2025=12-02
public class Test4 {
public static void main(String[] args) {
Animal tortoise = new Tortoise();
Animal wolf = new Wolf();
printAnimalSpecialFunction(tortoise);
printAnimalSpecialFunction(wolf);
}
// 打印动物独有功能
// 多态状态下,想调用`Tortoise / Wolf`的独有方法(不是方法重写的方法),是无法直接调用的
// 但是如果「强制类型转换」,那么就可以调用了(父类对象转为子类对象)
// 且 要用 instanceof 检查是否可以转
public static void printAnimalSpecialFunction(Animal animal) {
// animal.eatSheep(); // Error: 这是wolf的独有功能,没有进行「方法重写」所以无法调用
// animal.swim(); // Error: 这是tortoise的独有功能,没有进行「方法重写」所以无法调用
/* 传统写法 Java14以前的写法
if (animal instanceof Tortoise) {
// 如果传送来的对象可以转换为Tortoise对象,就可以强转后调用Tortoise的独有方法
Tortoise tortoise = (Tortoise) animal;
tortoise.swim(); // 调用 Tortoise 的独有方法
} else if (animal instanceof Wolf) {
// 如果传送来的对象可以转换为Wolf对象,就可以强转后调用Wolf的独有方法
Wolf wolf = (Wolf) animal;
wolf.eatSheep(); // 调用 Wolf 的独有方法
}
*/
// 新的写法:传统写法 Java14开始以后的写法
if (animal instanceof Tortoise tortoise) {
tortoise.swim();
} else if (animal instanceof Wolf wolf) {
wolf.eatSheep();
}
}
}
4 案例:
1)题目
加油站支付小模块
- 某加油站为了吸引更多的车主,推出了如下活动,车主可以办理金卡和银卡。
- 卡片信息包括:车牌号码、车主姓名、电话号码、卡片余额。
- 金卡办理时入存金额必须>=5000元,银卡办理时预存金额必须>=2000元,金卡支付时享受8折优惠,银卡支付时享受9折优惠,金卡消费满200元可以提供打印免费洗车票的服务。
- 需求:请使用面向对象编程,完成该加油站支付机的存款和消费程序。
2)代码
如下4个文件:
- Test.java - 执行
java
package zhenglonghao.gasstationpayment;
// 版本2
// Date: 2025-12-02
import java.util.ArrayList;
import java.util.List;
// 加油站支付
//1. 创建卡片类,用于封装车主的数据信息
//2. 定义一个父类 Card,包含金卡和银卡的共同属性和方法
// 3. 定义金卡类,继承 Card 类,重写消费方法(享受8折优惠),并实现独有功能:打印免费洗车票
// 4. 定义银卡类,继承 Card 类,重写消费方法(享受9折优惠)
// 5. 办理金卡:创建金卡对象,交给支付机完成存款和消费操作
//6. 办理银卡:创建银卡对象,交给支付机完成存款和消费操作
public class Test2 {
// 创建卡片集合
static List <Card> cardList = new ArrayList<>();
// 需要导入 Scanner 类
static java.util.Scanner scanner = new java.util.Scanner(System.in);
public static void main(String[] args) {
// 先存3个卡
cardList.add(new GoldCard("00001", "张三", 1234567890, 6000)); // 创建金卡对象
cardList.add(new SilverCard("00002", "李四", 1234567890, 5000));
cardList.add(new Card("00003", "王五", 1234567890, 4000));
while (true) {
System.out.println("======= 加油站支付 =======");
System.out.println("1. 添加卡片");
System.out.println("2. 充值");
System.out.println("3. 消费");
System.out.println("4. 查询余额");
System.out.println("5. 退出");
System.out.print("请选择:");
int choice = scanner.nextInt();
switch (choice) {
case 1: addCard1(); break;
case 2: recharge1(); break;
case 3: consume1(); break;
case 4: queryBalance1(); break;
case 5: System.out.println("退出系统"); return;
}
}
}
// 1.添加新的卡片的方法
public static void addCard1() {
System.out.println("请输入卡号:");
String cardId = scanner.next();
System.out.println("请输入卡名:");
String cardName = scanner.next();
System.out.println("请输入手机号:");
double phoneNumber = scanner.nextDouble();
System.out.println("请输入充值金额:(>=5000元为金卡,>=2000元为银卡,其余普通卡");
double money = scanner.nextDouble();
if (money >= 5000) { // 存入金卡
cardList.add(new GoldCard(cardId, cardName, phoneNumber, money));
System.out.println("添加成功,当前余额:" + money);
System.out.println("恭喜您成为金卡用户");
} else if (money >= 2000) {
cardList.add(new SilverCard(cardId, cardName, phoneNumber, money));
System.out.println("添加成功,当前余额:" + money);
System.out.println("恭喜您成为银卡用户");
} else {
cardList.add(new Card(cardId, cardName, phoneNumber, money));
System.out.println("添加成功,当前余额:" + money);
System.out.println("恭喜您成为普通用户");
}
}
// 2.充值的方法
public static void recharge1() {
System.out.println("请输入卡号:");
String cardId = scanner.next();
System.out.println("请输入充值金额:");
double money = scanner.nextDouble();
for (Card card : cardList) {
if (card.getCardId().equals(cardId)) { // 如果找到了cardId就会返回true 不要使用 card.getCardId() == cardId,因为字符串比较时,使用equals()方法
card.recharge(money);
return;
}
}
System.out.println("未找到该卡,请重新输入");
}
// 3.消费方法
public static void consume1() {
System.out.println("请输入卡号:");
String cardId = scanner.next();
System.out.println("请输入消费金额:");
double money = scanner.nextDouble();
for (Card card : cardList) {
if (card.getCardId().equals(cardId)) {
card.consume(money);
return;
}
}
System.out.println("未找到该卡,请重新输入");
}
// 4.查询余额的方法
public static void queryBalance1() {
System.out.println("请输入卡号:");
String cardId = scanner.next();
for (Card card : cardList) {
if (card.getCardId().equals(cardId)) {
System.out.println(card.getMoney());
return;
}
}
System.out.println("未找到该卡,请重新输入");
}
}
- Card.java - 父类
java
package zhenglonghao.gasstationpayment;
import lombok.AllArgsConstructor;
import lombok.Data; // 导包
import lombok.NoArgsConstructor;
// lombok 技术可以实现自动生成 getter 和 setter 方法、无参构造方法考(只有无参,没有有参)、toString 方法
@Data // @Data 注解会自动生成 getter 和 setter 方法、无参构造方法考(只有无参,没有有参)、toString 方法
@AllArgsConstructor // @AllArgsConstructor 注解会自动生成有参构造方法
@NoArgsConstructor // @NoArgsConstructor 注解会自动生成无参构造方法
public class Card { // 父类Card
private String cardId; // 卡号
private String cardName; // 卡名
private double phoneNumber; // 手机号
private double money; // 金钱
// 消费金额
public void consume(double money) {
// this.money -= money;
// 判断输入的消费金额是否正确
if (money < 0) { // 输入的金额小于0
System.out.println("消费金额不能小于0");
} else if (money > this.money) { // 金卡余额不足
System.out.println("余额不足");
} else { // 正常的消费
this.money -= money;
System.out.println("已消费:" + money + ", 当前余额为:" + this.money);
}
}
// 充值金额
public void recharge(double money) {
// 判断输入的充值金额是否符合现实规则
if (money < 0) {
System.out.println("充值金额不能小于0");
} else {
this.money += money;
System.out.println("充值成功,当前余额:" + this.money);
}
}
}
- GoldCard.java - 子类
java
package zhenglonghao.gasstationpayment;
// 金卡
// 金卡继承Card
// 金卡特有功能:8折优惠 + 打印免费洗车票
public class GoldCard extends Card {
// 金卡继承Card
public GoldCard(String cardId, String cardName, double phoneNumber, double money) {
super(cardId, cardName, phoneNumber, money);
}
// 多态: 重写消费方法
@Override
public void consume(double money) {
System.out.println("银卡用户消费享受九折优惠:");
// 优惠后的价格
double discountMoney = money * 0.8;
super.consume(discountMoney);
if (discountMoney >= 200) {
System.out.println("恭喜!您已获得一张免费洗车券");
}
}
// 刚开始多写了,此方法无需重写,因为子类已继承Card父类的recharge实现
// 也就是:当调用的子类方法不存在时,会在父类中查找,然后自动调用父类的方法
// // 多态: 重写充值方法
// @Override
// public void recharge(double money) {
// super.recharge(money);
// }
}
- SilverCard.java - 子类
java
package zhenglonghao.gasstationpayment;
// 银卡
// 银卡继承Card
// 银卡特有功能:8折优惠
public class SilverCard extends Card {
// 银卡继承Card
public SilverCard(String cardId, String cardName, double phoneNumber, double money) {
super(cardId, cardName, phoneNumber, money);
}
// 多态: 重写消费方法
@Override
public void consume(double money) {
System.out.println("银卡用户消费享受九折优惠:");
// 优惠后的价格
double discountMoney = money * 0.9;
super.consume(discountMoney);
}
// 刚开始多写了,此方法无需重写,因为子类已继承Card父类的recharge实现
// 也就是:当调用的子类方法不存在时,会在父类中查找,然后自动调用父类的方法
// // 多态: 重写充值方法
// @Override
// public void recharge(double money) {
// super.recharge(money);
// }
}
3)我的疑惑:
-
假如子类继承了父类 且 在该子类某方法重写中使用
super.父类方法(),且该父类方法是对this.成员进行的操作,那么我是操作的父类成员还是子类成员?(换成该代码中就是:在
ColdCard子类consume(double money)方法重写中,使用了super.consume()对this.money进行操作this.money -= money;,那么我操作的是「ColdCard创建的对象的成员」还是「使用Card创建的对象的成员」前提是对象是这样创建的:
Card XXX = new ColdCard(...)
因为使用了面向对象的「多态」特性 + 「方法重写」
当你在 SilverCard 或 GoldCard 类中调用 super.consume(discountMoney) 时,虽然你是在调用父类 Card 的 consume() 方法,但这个方法内部访问的是当前对象(也就是子类实例)中的字段。
也就是说:this.money -= money;
这句代码虽然是写在 Card 类里,但由于是被子类对象调用执行的,所以这里的 this 实际上指向的是子类对象本身。
因此它修改的就是子类对象自己的 money 成员 ------ 虽然成员是从父类继承来的
| 问题 | 解答 |
|---|---|
使用 super.method() 是不是操作父类成员? |
❌ 不是。你操作的是当前对象(子类对象)的成员变量,哪怕方法定义在父类中。 |
父类中写的 this.money 在子类调用时到底是谁的 money? |
✅ 是当前子类对象的 money。Java 中所有非静态方法都是基于对象调用的。 |
| 是否必须要有类的继承 + 方法重写才能做到这点? | ✅ 是的!这是面向对象三大特征之一:"多态"的体现。 |
-
假如调用了子类中没有的方法,那么会在父类中查找吗?
如充值的方法
recharge(double money),在父类中已有该方法假如在子类中不写该方法,也就是不进行方法重写
如果调用了该方法,是会自动调用父类中的方法吗?
- 如果子类没有该方法 → Java会自动在父类中查找对应方法
- 这称为方法继承 - 子类会继承父类的所有非私有方法
- 方法查找遵循继承链 - 会一直向上查找直到找到方法或到达Object类