乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

多态

本节定位:多态是面向对象编程的皇冠明珠。本节从"编译看左边、运行看右边"的经典口诀出发,深入 JVM 动态绑定机制(invokevirtual 指令、虚方法表查找),讲解多态在方法参数、返回值、集合中的实际应用,并澄清成员变量与静态方法的特殊绑定规则。


什么是多态

多态(Polymorphism):同一个行为具有多个不同表现形式或形态。在 Java 中,多态指父类引用指向子类对象,调用方法时表现出子类的行为。

多态的三个前提条件:

  1. 继承:存在父子类关系
  2. 重写:子类重写了父类的方法
  3. 父类引用指向子类对象:Parent p = new Child();

飞翔科技场景:薪资计算的多态应用

架构师白歌对实习生小崔说:"如果每新增一种员工类型,你都要修改薪资计算的主流程代码,那这个系统迟早会变成意大利面条。你应该用多态来解耦。"

重构前:没有多态的代码

// ❌ 反例:每新增员工类型都要修改这段代码
public class PayrollSystem {
    public double calculatePay(Object employee) {
        if (employee instanceof Manager) {
            Manager m = (Manager) employee;
            return m.getSalary() * 12 + m.getBonus();
        } else if (employee instanceof Developer) {
            Developer d = (Developer) employee;
            return d.getSalary() * 12;
        } else if (employee instanceof Designer) {  // 新增类型又要改这里!
            Designer ds = (Designer) employee;
            // ...
        }
        return 0;
    }
}

重构后:多态驱动的薪资系统

// ✅ 正确:利用多态,新增员工类型无需修改此方法
public class PayrollSystem {
    public double calculateAnnualPay(Employee emp) {
        return emp.computeAnnualSalary();  // 多态调用!根据实际类型动态绑定
    }

    public void printPayReport(Employee[] staff) {
        for (Employee emp : staff) {
            System.out.println(emp.getName() + " 年薪: " + calculateAnnualPay(emp));
        }
    }
}
// 完整测试
public class PolymorphismTest {
    public static void main(String[] args) {
        Employee[] staff = {
            new Manager("大翔", "E001", 50000, 200000),
            new Developer("小崔", "E002", 25000, "Java"),
            new Developer("白歌", "E003", 35000, "Python")
        };

        PayrollSystem ps = new PayrollSystem();
        ps.printPayReport(staff);
    }
}
运行输出:
大翔 年薪: 800000.0
小崔 年薪: 300000.0
白歌 年薪: 420000.0

"编译看左边,运行看右边"深度解析

这是 Java 多态最核心的口诀。

Employee emp = new Manager("大翔", "E001", 50000, 200000);
//  ↑左边(编译时类型)           ↑右边(运行时类型)

实操验证

public class CompileLeftRunRightTest {
    public static void main(String[] args) {
        Employee emp = new Manager("大翔", "E001", 50000, 200000);

        // --- 编译阶段(看左边 Employee) ---
        System.out.println(emp.getName());    // ✅ Employee 有 getName(),编译通过
        System.out.println(emp.work());       // ✅ Employee 有 work(),编译通过

        // emp.getBonus();  // ❌ Employee 没有 getBonus(),编译直接报错

        // --- 运行阶段(看右边 Manager) ---
        System.out.println(emp.work());
        // 输出:经理 大翔 正在制定项目计划...
        // 实际执行的是 Manager.work(),不是 Employee.work()
    }
}

JVM 底层:invokevirtual 指令与动态绑定

字节码视角

当 JVM 执行 emp.work() 时,生成的字节码指令是 invokevirtual:

字节码:
aload_1              // 将局部变量 emp 压入操作数栈
invokevirtual #5     // 调用 Employee.work() —— 常量池中符号引用是 Employee.work
                     // 但 JVM 在运行时会解析到实际类型(Manager)的方法

invokevirtual 的执行流程

核心原理:invokevirtual 指令在解析方法时,不是根据引用变量的声明类型,而是根据操作数栈顶对象的实际类型去查找方法。这就是"动态绑定"的本质。


instanceof 类型判断

instanceof 用于在运行时检查对象的实际类型,是向下转型前的安全守卫。

语法与使用

对象引用 instanceof 类型   // 返回 boolean
public class InstanceofTest {
    public static void main(String[] args) {
        Employee[] staff = {
            new Manager("大翔", "E001", 50000, 200000),
            new Developer("小崔", "E002", 25000, "Java"),
            new Developer("白歌", "E003", 35000, "Python")
        };

        for (Employee emp : staff) {
            if (emp instanceof Manager) {
                Manager m = (Manager) emp;
                System.out.println(m.getName() + " 是经理,奖金: " + m.getBonus());
            } else if (emp instanceof Developer) {
                Developer d = (Developer) emp;
                System.out.println(d.getName() + " 是开发者,语言: " + d.getProgrammingLanguage());
            }
        }
    }
}
运行输出:
大翔 是经理,奖金: 200000.0
小崔 是开发者,语言: Java
白歌 是开发者,语言: Python

instanceof 判断规则

情况结果
obj instanceof 同类true
obj instanceof 父类true(子类对象 is-a 父类)
obj instanceof 实现的接口true
obj instanceof 无关类编译错误
null instanceof 任何类false

多态的应用场景

场景一:方法参数多态

// 方法参数声明为父类类型,可接受所有子类对象
public class HRService {
    // 任何 Employee 子类都可以传入
    public void processLeave(Employee emp, int days) {
        System.out.println(emp.getName() + " 请假 " + days + " 天");
        // 多态调用:不同的员工 work() 行为不同
        System.out.println("  工作交接: " + emp.work());
    }
}

// 测试
public class ParamPolymorphismTest {
    public static void main(String[] args) {
        HRService hr = new HRService();
        hr.processLeave(new Manager("大翔", "E001", 50000, 200000), 3);
        hr.processLeave(new Developer("小崔", "E002", 25000, "Java"), 5);
    }
}
运行输出:
大翔 请假 3 天
  工作交接: 经理 大翔 正在制定项目计划...
小崔 请假 5 天
  工作交接: 开发者 小崔 正在用 Java 编写代码...

场景二:返回值多态

public class EmployeeFactory {
    /**
     * 根据类型创建员工——返回父类类型
     */
    public static Employee createEmployee(String type, String name, String id, double salary) {
        switch (type) {
            case "Manager":
                return new Manager(name, id, salary, 100000);  // 向上转型
            case "Developer":
                return new Developer(name, id, salary, "Java");
            default:
                return new Employee(name, id, salary);
        }
    }
}

// 测试
public class ReturnPolymorphismTest {
    public static void main(String[] args) {
        Employee emp = EmployeeFactory.createEmployee("Manager", "孔蓝", "E004", 40000);
        System.out.println(emp.work());  // 运行时多态:调用 Manager.work()
    }
}

场景三:集合多态

import java.util.*;

public class CollectionPolymorphismTest {
    public static void main(String[] args) {
        // 集合声明为父类类型 List,实际使用 ArrayList
        List<Employee> staff = new ArrayList<>();  // 集合多态

        staff.add(new Manager("大翔", "E001", 50000, 200000));
        staff.add(new Developer("小崔", "E002", 25000, "Java"));

        // List 接口声明的方法,ArrayList 具体实现
        for (Employee emp : staff) {
            System.out.println(emp.getName() + " → " + emp.computeAnnualSalary());
        }
    }
}

特殊情况:成员变量与静态方法

成员变量:编译和运行都看左边!

public class Employee {
    public String tag = "普通员工";
}

public class Manager extends Employee {
    public String tag = "管理层";  // 隐藏父类字段,而非重写
}

public class FieldPolymorphismTest {
    public static void main(String[] args) {
        Employee emp = new Manager();
        System.out.println(emp.tag);  // 输出:普通员工 ← 看左边(Employee)
        // 不是"管理层"!因为字段访问是编译时绑定
    }
}
运行输出:
普通员工

静态方法:编译和运行都看左边!

public class Employee {
    public static String getOrganization() {
        return "飞翔科技技术部";
    }
}

public class Manager extends Employee {
    public static String getOrganization() {  // 隐藏父类静态方法,而非重写
        return "飞翔科技管理层";
    }
}

public class StaticMethodPolymorphismTest {
    public static void main(String[] args) {
        Employee emp = new Manager();
        System.out.println(emp.getOrganization());  // 输出:飞翔科技技术部 ← 看左边
        // 不是"飞翔科技管理层"!因为静态方法是编译时绑定
    }
}
运行输出:
飞翔科技技术部

孔蓝总结:只有实例方法是运行时绑定(多态),成员变量和静态方法都是编译时绑定——取决于引用变量的声明类型。


多态绑定规则总表

访问的成员类型编译时运行时绑定方式
实例方法(非 private / final / static)看左边(引用类型)看右边(实际类型) ✅动态绑定
成员变量看左边看左边静态绑定
static 方法看左边看左边静态绑定
private 方法看左边看左边静态绑定
final 方法看左边看左边(理论上,但 JVM 可能内联)静态绑定

易错场景与避坑指南

反例一:多态引用直接调用子类特有方法

// ❌ 错误:编译通不过
Employee emp = new Manager("大翔", "E001", 50000, 200000);
// double bonus = emp.getBonus();  // ❌ Employee 没有 getBonus() 方法

纠正:需要向下转型:

// ✅ 正确:先判断再转型
if (emp instanceof Manager) {
    Manager m = (Manager) emp;
    double bonus = m.getBonus();
}

反例二:构造方法中触发多态调用

// ❌ 危险:父类构造方法中调用了被子类重写的方法
public class Employee {
    public Employee() {
        logCreation();  // 多态调用——危险!
    }

    public void logCreation() {
        System.out.println("Employee created");
    }
}

public class Manager extends Employee {
    private String department;

    public Manager(String department) {
        // super() 先执行,此时 department 还未赋值
        this.department = department;
    }

    @Override
    public void logCreation() {
        // department 此时为 null!
        System.out.println("Manager created, department: " + department);
    }
}

public class ConstructorPolymorphismTrap {
    public static void main(String[] args) {
        Manager m = new Manager("技术部");
        // 输出:Manager created, department: null  ← 注意!
    }
}

纠正:父类构造方法中只调用 private、final 或 static 方法。

反例三:重载方法的多态歧义

public class OverloadAmbiguity {
    public void process(Employee emp) {
        System.out.println("处理员工: " + emp.getName());
    }

    public void process(Manager mgr) {
        System.out.println("处理经理: " + mgr.getName() + " 奖金: " + mgr.getBonus());
    }

    public static void main(String[] args) {
        OverloadAmbiguity oa = new OverloadAmbiguity();

        Employee emp = new Manager("大翔", "E001", 50000, 200000);
        oa.process(emp);  // 输出:处理员工: 大翔
        // ❓ 为什么不是"处理经理"?

        // 原因:重载方法选择在编译期决定,看的是引用类型
    }
}

白歌点评:重载(Overload)是编译时决定,多态(Override)是运行时决定。这两者混用时,要特别注意重载的方法选择看的是引用类型。


面试考点

Q1:多态的三个前提条件是什么?

① 存在继承关系(子类继承父类);② 子类重写了父类的方法(非 private / final / static 的实例方法);③ 父类引用指向子类对象(如 Employee e = new Manager())。

Q2:"编译看左边,运行看右边"在什么情况下不成立?

对于成员变量和静态方法,编译和运行都看左边(引用类型),因为它们是静态绑定的。对于private 和 final 方法,同样不存在运行时的多态。

Q3:多态在 JVM 层面是如何实现的?

JVM 通过**虚方法表(vtable)**实现动态绑定。每个类在方法区维护一张 vtable,存储该类所有可被重写的实例方法入口地址。当执行 invokevirtual 指令时,JVM 根据操作数栈顶对象的实际类型(通过对象头中的类型指针),查找对应类的 vtable,找到具体方法地址后跳转执行。

Q4:为什么不能在构造方法中调用可重写的实例方法?

因为父类构造方法执行时,子类构造方法还未执行,子类字段处于默认值状态。如果父类构造方法调用了被子类重写的方法,该方法会读到子类字段的默认值(null / 0 / false),而非预期的初始化值,导致难以调试的 bug。

Q5:instanceof 和 getClass() 有什么区别?

instanceof 检查对象是否是某个类或其子类的实例("is-a" 关系),而 getClass() 返回对象的精确运行时类。emp instanceof Employee 对 Manager 对象也返回 true,但 emp.getClass() == Employee.class 对 Manager 对象返回 false。


上一页
final 关键字
下一页
向上转型与向下转型