编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
      • 流程语句
      • 函数方法
      • 类和对象
      • 继承和多态
        • 8.1 继承基础
          • 8.1.1 继承基本概念
          • 8.1.2 继承的语法
          • 8.1.3 Java单继承
          • 8.1.4 继承中的构造方法
          • 8.1.5 综合案例:员工继承体系
          • 8.1.6 继承基础训练题
        • 8.2 方法重写
          • 8.2.1 方法重写概念
          • 8.2.2 Override注解
          • 8.2.3 重写和重载区别
          • 8.2.4 综合案例:动物叫声重写
          • 8.2.5 方法重写训练题
        • 8.3 super关键字
          • 8.3.1 super访问父类
          • 8.3.2 super调用构造方法
          • 8.3.3 综合案例:super调用链演示
          • 8.3.4 super关键字训练题
        • 8.4 Object类
          • 8.4.1 Object是所有类的父类
          • 8.4.2 toString方法
          • 8.4.3 equals方法
          • 8.4.4 hashCode方法
          • 8.4.5 综合案例:正确重写equals和hashCode
          • 8.4.6 训练题
        • 8.5 多态
          • 8.5.1 多态基本概念
          • 8.5.2 向上转型和向下转型
          • 8.5.3 多态案例
          • 8.5.4 多态的好处
          • 8.5.5 综合案例:图形面积计算器(多态)
          • 8.5.6 多态训练题
      • 接口和抽象类
      • 异常处理
      • 集合框架
      • IO流和File
      • 线程和锁
      • 泛型
      • 注解和反射
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

继承和多态

# 08.继承和多态

# 目录介绍

  • 8.1 继承基础
    • 8.1.1 继承基本概念
    • 8.1.2 继承的语法
    • 8.1.3 Java单继承
    • 8.1.4 继承中的构造方法
    • 8.1.5 综合案例:员工继承体系
    • 8.1.6 继承基础训练题
  • 8.2 方法重写
    • 8.2.1 方法重写概念
    • 8.2.2 Override注解
    • 8.2.3 重写和重载区别
    • 8.2.4 综合案例:动物叫声重写
    • 8.2.5 方法重写训练题
  • 8.3 super关键字
    • 8.3.1 super访问父类
    • 8.3.2 super调用构造方法
    • 8.3.3 综合案例:super调用链演示
    • 8.3.4 super关键字训练题
  • 8.4 Object类
    • 8.4.1 Object是所有类的父类
    • 8.4.2 toString方法
    • 8.4.3 equals方法
    • 8.4.4 hashCode方法
    • 8.4.5 综合案例:正确重写equals和hashCode
    • 8.4.6 训练题
  • 8.5 多态
    • 8.5.1 多态基本概念
    • 8.5.2 向上转型和向下转型
    • 8.5.3 多态案例
    • 8.5.4 多态的好处
    • 8.5.5 综合案例:图形面积计算器(多态)
    • 8.5.6 多态训练题

# 8.1 继承基础

# 8.1.1 继承基本概念

继承是面向对象编程的核心特性之一,允许一个类(子类/派生类)基于另一个类(父类/基类)创建,从而复用父类的属性和方法。

父类(基类):  Animal
                ↑
子类(派生类):Dog, Cat
1
2
3

# 8.1.2 继承的语法

使用 extends 关键字实现继承:

// 父类
public class Animal {
    protected String name;
    protected int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(name + " 在吃东西");
    }
}

// 子类
public class Dog extends Animal {
    private String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);  // 调用父类构造方法
        this.breed = breed;
    }

    public void bark() {
        System.out.println(name + " 在汪汪叫");
    }
}

Dog dog = new Dog("旺财", 3, "金毛");
dog.eat();   // 继承自 Animal
dog.bark();  // Dog 自己的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

对比 C++:C++ 使用 : 加访问修饰符继承(class Dog : public Animal),Java 使用 extends。C++ 没有访问修饰符限制,默认 private 继承;Java 只有 public 继承。

# 8.1.3 Java单继承

Java 只支持单继承,一个类只能有一个直接父类:

// 正确:单继承
class Dog extends Animal {}

// 错误:Java 不支持多继承
// class Dog extends Animal, Pet {}  // 编译错误

// 但可以通过接口实现"多继承"的效果
class Dog extends Animal implements Pet, Trainable {}
1
2
3
4
5
6
7
8

对比 C++:C++ 支持多继承(class Dog : public Animal, public Pet {}),但多继承容易导致菱形继承问题。Java 通过接口避免了这个问题。

# 8.1.4 继承中的构造方法

public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
        System.out.println("Animal 构造方法");
    }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);  // 必须在第一行调用父类构造方法
        System.out.println("Dog 构造方法");
    }
}

Dog dog = new Dog("旺财");
// 输出:
// Animal 构造方法
// Dog 构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

构造顺序:先父类后子类。如果父类没有无参构造方法,子类必须显式调用 super(参数)。

# 8.1.5 综合案例:员工继承体系

编写一个员工类继承体系,展示继承的基本语法、构造方法链和代码复用。

public class EmployeeDemo {
    static class Employee {
        protected String name;
        protected double baseSalary;

        Employee(String name, double baseSalary) {
            this.name = name;
            this.baseSalary = baseSalary;
            System.out.println("[Employee构造] " + name);
        }

        public double getSalary() { return baseSalary; }
        public String toString() { return name + ", 薪资: ¥" + getSalary(); }
    }

    static class Manager extends Employee {
        private double bonus;

        Manager(String name, double baseSalary, double bonus) {
            super(name, baseSalary);  // 调用父类构造
            this.bonus = bonus;
            System.out.println("[Manager构造] 奖金: " + bonus);
        }

        public double getSalary() { return baseSalary + bonus; }
    }

    static class Developer extends Employee {
        private int overtimeHours;

        Developer(String name, double baseSalary, int overtimeHours) {
            super(name, baseSalary);
            this.overtimeHours = overtimeHours;
        }

        public double getSalary() { return baseSalary + overtimeHours * 100; }
    }

    public static void main(String[] args) {
        System.out.println("===== 创建对象(观察构造链) =====");
        Employee emp = new Employee("张三", 8000);
        Manager mgr = new Manager("李四", 12000, 5000);
        Developer dev = new Developer("王五", 10000, 20);

        System.out.println("\n===== 员工信息 =====");
        Employee[] team = {emp, mgr, dev};
        for (Employee e : team) {
            System.out.println("  " + e);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 8.1.6 继承基础训练题

训练1:创建一个三层继承链:Vehicle → Car → ElectricCar。Vehicle 有属性 brand 和 maxSpeed,Car 新增 fuelType,ElectricCar 新增 batteryCapacity。编写构造方法并测试构造顺序。

训练2:以下代码输出什么?请画出构造方法的调用链:

class A { A() { System.out.println("A"); } }
class B extends A { B() { System.out.println("B"); } }
class C extends B { C() { System.out.println("C"); } }
new C();
1
2
3
4

思考:Java 中子类构造方法的第一行必须是 super() 或 this()。如果两者都不写,编译器会自动插入 super()。这种强制机制保证了什么?(提示:父类字段的初始化安全)

# 8.2 方法重写

# 8.2.1 方法重写概念

子类可以重写(Override)父类的方法,提供自己的实现:

public class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("汪汪汪!");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵喵!");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 8.2.2 Override注解

@Override 注解不是必须的,但强烈推荐使用。它让编译器帮你检查是否正确重写了父类方法:

@Override
public void makeSound() {  // 如果方法签名写错,编译器会报错
    System.out.println("汪汪汪!");
}
1
2
3
4

# 8.2.3 重写和重载区别

对比项 重写(Override) 重载(Overload)
发生位置 子类和父类之间 同一个类中
方法名 相同 相同
参数列表 相同 必须不同
返回类型 相同或协变类型 可以不同
访问修饰符 不能更严格 无限制

# 8.2.4 综合案例:动物叫声重写

编写一个程序,展示方法重写的正确写法、@Override 注解的作用,以及重写与重载的对比。

public class OverrideDemo {
    static class Animal {
        public String speak() { return "..."; }
        public String toString() { return "Animal"; }
    }

    static class Dog extends Animal {
        @Override
        public String speak() { return "汪汪汪!"; }
        @Override
        public String toString() { return "狗"; }
    }

    static class Cat extends Animal {
        @Override
        public String speak() { return "喵喵喵~"; }
        @Override
        public String toString() { return "猫"; }

        // 重载(不是重写): 参数不同
        public String speak(int times) {
            return "喵~".repeat(times);
        }
    }

    public static void main(String[] args) {
        Animal[] animals = {new Dog(), new Cat(), new Animal()};
        System.out.println("===== 方法重写 =====");
        for (Animal a : animals) {
            System.out.println(a + " 说: " + a.speak());  // 运行时调用实际类型的方法
        }

        System.out.println("\n===== 重写vs重载 =====");
        Cat cat = new Cat();
        System.out.println("重写(无参): " + cat.speak());
        System.out.println("重载(3次): " + cat.speak(3));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 8.2.5 方法重写训练题

训练1:以下代码能编译通过吗?如果不能,如何修改?

class Parent {
    Object getValue() { return "parent"; }
}
class Child extends Parent {
    String getValue() { return "child"; }  // 协变返回类型?
}
1
2
3
4
5
6

训练2:子类重写方法时,访问权限只能放大不能缩小。请编写代码验证:父类方法是 protected,子类重写为 private 会发生什么?

思考:static 方法能被重写吗?如果父类和子类有同名同参数的 static 方法,这叫什么?(提示:隐藏 hiding vs 重写 overriding)

# 8.3 super关键字

# 8.3.1 super访问父类

public class Dog extends Animal {
    @Override
    public void makeSound() {
        super.makeSound();  // 调用父类的方法
        System.out.println("汪汪汪!");
    }
}
1
2
3
4
5
6
7

# 8.3.2 super调用构造方法

public class Dog extends Animal {
    private String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);  // 调用父类构造方法,必须在第一行
        this.breed = breed;
    }
}
1
2
3
4
5
6
7
8

对比 C++:C++ 使用初始化列表调用父类构造 Dog(string name) : Animal(name) {},Java 使用 super() 在构造方法体中调用。

# 8.3.3 综合案例:super调用链演示

编写一个三层继承体系,展示 super 访问父类属性、方法和构造方法的完整用法。

public class SuperChainDemo {
    static class Vehicle {
        protected String brand;
        Vehicle(String brand) {
            this.brand = brand;
            System.out.println("Vehicle(" + brand + ")");
        }
        public String info() { return "品牌: " + brand; }
    }

    static class Car extends Vehicle {
        protected int seats;
        Car(String brand, int seats) {
            super(brand);  // 调用父类构造
            this.seats = seats;
            System.out.println("Car(seats=" + seats + ")");
        }
        @Override
        public String info() {
            return super.info() + ", 座位: " + seats;  // super调用父类方法
        }
    }

    static class ElectricCar extends Car {
        private int battery;
        ElectricCar(String brand, int seats, int battery) {
            super(brand, seats);  // 调用Car的构造
            this.battery = battery;
            System.out.println("ElectricCar(battery=" + battery + ")");
        }
        @Override
        public String info() {
            return super.info() + ", 电池: " + battery + "kWh";
        }
    }

    public static void main(String[] args) {
        System.out.println("===== 构造方法链(自顶向下) =====");
        ElectricCar tesla = new ElectricCar("Tesla", 5, 75);

        System.out.println("\n===== super方法链 =====");
        System.out.println(tesla.info());
        // 输出: 品牌: Tesla, 座位: 5, 电池: 75kWh
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 8.3.4 super关键字训练题

训练1:编写一个 LoggingList 继承 ArrayList,重写 add() 方法,在调用 super.add() 之前打印日志信息。

训练2:this 和 super 的区别是什么?以下代码中 this() 和 super() 能同时出现在一个构造方法中吗?为什么?

class Child extends Parent {
    Child() {
        // super();  // 调用父类构造
        // this(10); // 调用本类其他构造
        // 能同时存在吗?
    }
    Child(int x) { super(); }
}
1
2
3
4
5
6
7
8

思考:super 关键字不是一个对象引用,不能赋给变量(Object o = super; 编译错误)。它在JVM层面是如何实现的?(提示:编译器在编译时就确定了 super 指向的父类方法)

# 8.4 Object类

# 8.4.1 Object是所有类的父类

Java 中所有类都直接或间接继承自 Object 类。如果一个类没有显式 extends,则默认继承 Object。

public class Account {  // 等价于 public class Account extends Object
}
1
2

# 8.4.2 toString方法

public class Account {
    private String name;
    private double balance;

    @Override
    public String toString() {
        return "Account{name='" + name + "', balance=" + balance + "}";
    }
}

Account acc = new Account("张三", 1000);
System.out.println(acc);  // 自动调用 toString()
// 输出:Account{name='张三', balance=1000.0}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 8.4.3 equals方法

public class Account {
    private String name;
    private double balance;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Account other = (Account) obj;
        return name.equals(other.name) && Double.compare(balance, other.balance) == 0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 8.4.4 hashCode方法

重写 equals 必须同时重写 hashCode:

@Override
public int hashCode() {
    return Objects.hash(name, balance);
}
1
2
3
4

# 8.4.5 综合案例:正确重写equals和hashCode

编写一个 Point 类,正确重写 toString、equals 和 hashCode 方法,并验证其行为。

import java.util.HashSet;
import java.util.Objects;

public class ObjectMethodDemo {
    static class Point {
        private int x, y;

        Point(int x, int y) { this.x = x; this.y = y; }

        @Override
        public String toString() {
            return "Point(" + x + ", " + y + ")";
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (!(obj instanceof Point other)) return false;
            return this.x == other.x && this.y == other.y;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }
    }

    public static void main(String[] args) {
        Point p1 = new Point(3, 4);
        Point p2 = new Point(3, 4);
        Point p3 = new Point(5, 6);

        // toString
        System.out.println("===== toString =====");
        System.out.println(p1);  // 自动调用toString

        // equals
        System.out.println("\n===== equals =====");
        System.out.println("p1.equals(p2): " + p1.equals(p2));  // true
        System.out.println("p1.equals(p3): " + p1.equals(p3));  // false
        System.out.println("p1 == p2: " + (p1 == p2));          // false (不同对象)

        // hashCode: equals相等→hashCode必须相等
        System.out.println("\n===== hashCode =====");
        System.out.println("p1.hashCode: " + p1.hashCode());
        System.out.println("p2.hashCode: " + p2.hashCode());
        System.out.println("相等: " + (p1.hashCode() == p2.hashCode()));

        // HashSet验证
        System.out.println("\n===== HashSet去重 =====");
        HashSet<Point> set = new HashSet<>();
        set.add(p1);
        set.add(p2);  // 因equals相等,不会重复添加
        set.add(p3);
        System.out.println("集合大小: " + set.size());  // 2
        System.out.println("包含(3,4): " + set.contains(new Point(3, 4)));  // true
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 8.4.6 训练题

  1. 实践题:创建一个 Student 类(姓名、学号),重写 toString()、equals()、hashCode() 三个方法。然后创建两个属性相同的 Student 对象,验证它们放入 HashSet 后是否被视为同一个元素。

  2. 代码题:以下代码的输出是什么?请解释原因:

    Object obj = new Object();
    System.out.println(obj.toString());
    System.out.println(obj.hashCode() == System.identityHashCode(obj));
    
    1
    2
    3
  3. 思考题:Object 类的 clone() 方法是 protected 的,且要求实现 Cloneable 接口。为什么 Java 这样设计而不是把 clone() 设为 public?浅拷贝和深拷贝的区别是什么?

# 8.5 多态

# 8.5.1 多态基本概念

多态:同一个方法在不同对象上有不同的表现形式。多态的前提:

  1. 有继承关系
  2. 子类重写父类方法
  3. 父类引用指向子类对象
Animal animal1 = new Dog("旺财", 3, "金毛");   // 父类引用指向子类对象
Animal animal2 = new Cat("咪咪", 2);

animal1.makeSound();  // 汪汪汪!(调用的是 Dog 的方法)
animal2.makeSound();  // 喵喵喵!(调用的是 Cat 的方法)
1
2
3
4
5

# 8.5.2 向上转型和向下转型

// 向上转型(自动):子类 → 父类
Animal animal = new Dog("旺财", 3, "金毛");
animal.makeSound();  // 可以调用 Animal 中声明的方法
// animal.bark();    // 编译错误!Animal 没有 bark 方法

// 向下转型(强制):父类 → 子类
if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.bark();  // 现在可以调用 Dog 特有的方法了
}

// JDK 16+ 模式匹配
if (animal instanceof Dog dog) {
    dog.bark();  // 直接使用
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 8.5.3 多态案例

public class Main {
    // 多态的典型应用:方法参数使用父类类型
    public static void animalShow(Animal animal) {
        animal.makeSound();  // 运行时根据实际类型调用对应方法
    }

    public static void main(String[] args) {
        animalShow(new Dog("旺财", 3, "金毛"));  // 汪汪汪!
        animalShow(new Cat("咪咪", 2));          // 喵喵喵!
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 8.5.4 多态的好处

  1. 提高代码可扩展性:新增子类不需要修改已有代码。
  2. 降低耦合度:调用方只依赖父类/接口,不依赖具体实现。
  3. 统一接口:通过父类类型统一管理不同子类对象。

对比 C++:C++ 通过虚函数(virtual)实现多态,Java 中所有非 static、非 final、非 private 方法默认都是"虚方法",不需要 virtual 关键字。

多态的底层原理(方法分派):JVM 通过虚方法表(vtable) 实现多态。每个类在加载时都会建立一张虚方法表,表中存储了该类所有可被动态调用的方法的入口地址。子类的虚方法表会继承父类的表项,重写的方法会替换为子类的实现地址。调用时,JVM 使用 invokevirtual 指令根据对象实际类型的虚方法表找到方法入口——这就是动态绑定(晚期绑定)。而字段访问、static 方法、private 方法和构造方法则是静态绑定(编译期确定),这也是为什么 p.name 访问的是父类字段而 p.show() 调用的是子类方法。

# 8.5.5 综合案例:图形面积计算器(多态)

编写一个程序,用多态实现不同图形的面积计算,展示向上转型、向下转型和多态的优势。

public class PolymorphismDemo {
    static abstract class Shape {
        abstract double area();
        abstract String shapeName();
    }

    static class Circle extends Shape {
        double radius;
        Circle(double r) { this.radius = r; }
        double area() { return Math.PI * radius * radius; }
        String shapeName() { return "圆(r=" + radius + ")"; }
    }

    static class Rectangle extends Shape {
        double w, h;
        Rectangle(double w, double h) { this.w = w; this.h = h; }
        double area() { return w * h; }
        String shapeName() { return "矩形(" + w + "×" + h + ")"; }
    }

    static class Triangle extends Shape {
        double base, height;
        Triangle(double b, double h) { this.base = b; this.height = h; }
        double area() { return 0.5 * base * height; }
        String shapeName() { return "三角形(底=" + base + ",高=" + height + ")"; }
    }

    // 多态:接收父类类型,处理所有子类
    static void printArea(Shape shape) {
        System.out.printf("  %s → 面积 = %.2f%n", shape.shapeName(), shape.area());
    }

    public static void main(String[] args) {
        // 向上转型:子类对象赋给父类引用
        Shape[] shapes = {
            new Circle(5),
            new Rectangle(4, 6),
            new Triangle(3, 8),
            new Circle(10)
        };

        System.out.println("===== 多态计算面积 =====");
        double totalArea = 0;
        for (Shape s : shapes) {
            printArea(s);  // 同一个方法,不同表现
            totalArea += s.area();
        }
        System.out.printf("总面积: %.2f%n", totalArea);

        // 向下转型(需要instanceof检查)
        System.out.println("\n===== 向下转型 =====");
        for (Shape s : shapes) {
            if (s instanceof Circle c) {  // JDK16+ 模式匹配
                System.out.println("  圆的半径: " + c.radius);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 8.5.6 多态训练题

训练1:以下代码中 animal.eat() 调用的是哪个版本?请从虚方法表角度解释:

class Animal { void eat() { System.out.println("Animal eat"); } }
class Dog extends Animal { void eat() { System.out.println("Dog eat"); } }
class GoldenRetriever extends Dog { void eat() { System.out.println("Golden eat"); } }

Animal animal = new GoldenRetriever();
animal.eat();  // 输出什么?
1
2
3
4
5
6

训练2:编写一个 Shape[] 数组,包含圆形、矩形、三角形等多种图形,使用多态调用 area() 方法计算总面积。然后使用 instanceof 分别统计每种图形的个数。

疑惑:多态中,成员变量是否也有动态绑定?

答疑:没有! Java 中成员变量是静态绑定的,在编译期就根据引用的声明类型决定访问哪个字段。只有方法才有动态绑定。

论证:

class Parent {
    int value = 10;
    void show() { System.out.println("Parent show"); }
}
class Child extends Parent {
    int value = 20;
    void show() { System.out.println("Child show"); }
}

Parent p = new Child();
System.out.println(p.value);  // 10(静态绑定,看声明类型 Parent)
p.show();                      // Child show(动态绑定,看实际类型 Child)
1
2
3
4
5
6
7
8
9
10
11
12

结果展示:p.value 输出 10 而不是 20,证明字段访问不走虚方法表,而是编译期直接确定。这个行为是 Java 面试的经典考题。

上次更新: 2026/06/10, 11:13:41
类和对象
接口和抽象类

← 类和对象 接口和抽象类→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式