【Java基础】多态 | 打卡day2

- 第 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. 多态是基本了解

多态是在继承/实现情况下的一种现象, 表现为:对象多态、行为多态

多态的核心规则

  • 方法调用编译 时检查「父类」方法签名,运行时执行「子类」重写的方法
  • 成员变量访问编译 时和运行时都访问「父类」中的成员变量

多态的实现条件

  • 继承关系 :存在extendsimplements关系

  • 向上转型父类引用指向子类对象

    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 多态的好处

  1. 右边对象解耦(可以任意更换子类对象)

    什么意思呢?

    就是右边的子类对象,可以任意更换(前提是满足多态的条件)

    可以换乌龟,狼,或者其他动物

    java 复制代码
    Animal tortoise = new Tortoise()
    Animal wolf = new Wolf();
  2. 父类对象做某方法的参数时,可以传入任意子类对象(如下方法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)我的疑惑:

  1. 假如子类继承了父类 且 在该子类某方法重写中使用super.父类方法(),且该父类方法是对this.成员进行的操作,那么我是操作的父类成员还是子类成员?

    (换成该代码中就是:在ColdCard子类consume(double money)方法重写中,使用了super.consume()this.money进行操作this.money -= money;,那么我操作的是「ColdCard创建的对象的成员」还是「使用Card创建的对象的成员」

    前提是对象是这样创建的:Card XXX = new ColdCard(...)

因为使用了面向对象的「多态」特性 + 「方法重写」

当你在 SilverCard 或 GoldCard 类中调用 super.consume(discountMoney) 时,虽然你是在调用父类 Cardconsume() 方法,但这个方法内部访问的是当前对象(也就是子类实例)中的字段。

也就是说:this.money -= money;

这句代码虽然是写在 Card 类里,但由于是被子类对象调用执行的,所以这里的 this 实际上指向的是子类对象本身。

因此它修改的就是子类对象自己的 money 成员 ------ 虽然成员是从父类继承来的

问题 解答
使用 super.method() 是不是操作父类成员? ❌ 不是。你操作的是当前对象(子类对象)的成员变量,哪怕方法定义在父类中。
父类中写的 this.money 在子类调用时到底是谁的 money ✅ 是当前子类对象的 moneyJava 中所有非静态方法都是基于对象调用的。
是否必须要有类的继承 + 方法重写才能做到这点? ✅ 是的!这是面向对象三大特征之一:"多态"的体现。
  1. 假如调用了子类中没有的方法,那么会在父类中查找吗?

    如充值的方法recharge(double money),在父类中已有该方法

    假如在子类中不写该方法,也就是不进行方法重写

    如果调用了该方法,是会自动调用父类中的方法吗?

  • 如果子类没有该方法 → Java会自动在父类中查找对应方法
  • 这称为方法继承 - 子类会继承父类的所有非私有方法
  • 方法查找遵循继承链 - 会一直向上查找直到找到方法或到达Object类
相关推荐
孞㐑¥6 小时前
算法——BFS
开发语言·c++·经验分享·笔记·算法
Re.不晚6 小时前
JAVA进阶之路——无奖问答挑战2
java·开发语言
八零后琐话6 小时前
干货:程序员必备性能分析工具——Arthas火焰图
开发语言·python
3GPP仿真实验室6 小时前
【MATLAB源码】CORDIC-QR :基于Cordic硬件级矩阵QR分解
开发语言·matlab·矩阵
知南x6 小时前
【Ascend C系列课程(高级)】(1) 算子调试+调优
c语言·开发语言
忆~遂愿6 小时前
GE 引擎与算子版本控制:确保前向兼容性与图重写策略的稳定性
大数据·开发语言·docker
Ro Jace7 小时前
计算机专业基础教材
java·开发语言
代码游侠7 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
mango_mangojuice7 小时前
Linux学习笔记(make/Makefile)1.23
java·linux·前端·笔记·学习