> 技术文档 > 《Java 程序设计》第 7 章 - 继承与多态

《Java 程序设计》第 7 章 - 继承与多态


引言

        在 Java 面向对象编程中,继承多态是两大核心特性,它们共同支撑了代码的复用性、扩展性和灵活性。本章将从继承的基本实现开始,逐步深入到方法覆盖、访问控制、抽象类等概念,最终揭示多态的本质与应用。通过大量可运行的代码示例和直观的图表,帮助你彻底掌握这些重要知识点。

7.1 类的继承

        继承是面向对象编程的三大特性之一,它允许我们基于已有的类创建新类,从而实现代码复用和扩展。被继承的类称为父类(超类),新创建的类称为子类(派生类)

7.1.1 类继承的实现

在 Java 中,使用extends关键字实现类的继承,语法如下:

class 子类名 extends 父类名 { // 子类新增属性和方法}

核心特点

  • 子类拥有父类的非私有属性和方法(代码复用)
  • 子类可以新增自己的属性和方法(功能扩展)
  • Java 只支持单继承(一个子类只能有一个直接父类)

代码示例:基础继承实现

// 父类:动物class Animal { // 父类属性 protected String name; // 父类方法 public void eat() { System.out.println(name + \"正在吃东西\"); } public void sleep() { System.out.println(name + \"正在睡觉\"); }}// 子类:狗(继承自动物)class Dog extends Animal { // 子类新增属性 private String breed; // 品种 // 子类新增方法 public void bark() { System.out.println(name + \"在汪汪叫\"); } // 子类setter方法(设置名字和品种) public void setInfo(String name, String breed) { this.name = name; // 直接访问父类的protected属性 this.breed = breed; } public void showBreed() { System.out.println(\"品种:\" + breed); }}// 测试类public class InheritanceDemo { public static void main(String[] args) { // 创建子类对象 Dog dog = new Dog(); dog.setInfo(\"旺财\", \"金毛\"); // 调用父类继承的方法 dog.eat(); // 输出:旺财正在吃东西 dog.sleep(); // 输出:旺财正在睡觉 // 调用子类新增的方法 dog.bark(); // 输出:旺财在汪汪叫 dog.showBreed();// 输出:品种:金毛 }}

类图:Animal 与 Dog 的继承关系

@startumlclass Animal { - String name + void eat() + void sleep()}class Dog { - String breed + void bark() + void setInfo(String, String) + void showBreed()}Animal <|-- Dog : extends@enduml

7.1.2 方法覆盖

        当子类需要修改父类的方法实现时,可以使用方法覆盖(Override),也称为方法重写。

方法覆盖的规则

  1. 方法名、参数列表必须与父类完全相同
  2. 返回值类型:父类返回值为T,子类可以是TT的子类(协变返回)
  3. 访问权限:子类方法权限不能低于父类(如父类protected,子类可protectedpublic
  4. 不能抛出比父类更多的 checked 异常
  5. @Override注解显式声明(非必须,但推荐,编译器会校验正确性)

代码示例:方法覆盖实现

// 父类:形状class Shape { // 父类方法:计算面积(默认实现) public double calculateArea() { System.out.println(\"形状的面积计算\"); return 0.0; }}// 子类:圆形(重写面积计算方法)class Circle extends Shape { private double radius; // 半径 public Circle(double radius) { this.radius = radius; } // 重写父类的面积计算方法 @Override public double calculateArea() { System.out.println(\"圆形的面积计算\"); return Math.PI * radius * radius; // 圆面积公式:πr² }}// 子类:矩形(重写面积计算方法)class Rectangle extends Shape { private double length; // 长 private double width; // 宽 public Rectangle(double length, double width) { this.length = length; this.width = width; } // 重写父类的面积计算方法 @Override public double calculateArea() { System.out.println(\"矩形的面积计算\"); return length * width; // 矩形面积公式:长×宽 }}// 测试类public class OverrideDemo { public static void main(String[] args) { Shape circle = new Circle(5); System.out.println(\"圆面积:\" + circle.calculateArea()); // 输出:圆形的面积计算 圆面积:78.539... Shape rectangle = new Rectangle(4, 6); System.out.println(\"矩形面积:\" + rectangle.calculateArea()); // 输出:矩形的面积计算 矩形面积:24.0 }}

7.1.3 super 关键字

super关键字用于访问父类的属性、方法和构造器,主要场景:

  • 调用父类的非私有属性:super.属性名
  • 调用父类的非私有方法:super.方法名(参数)
  • 调用父类的构造器:super(参数)(必须放在子类构造器第一行)

代码示例:super 关键字的使用

// 父类:员工class Employee { protected String name; protected double salary; // 父类构造器 public Employee(String name, double salary) { this.name = name; this.salary = salary; } // 父类方法 public void showInfo() { System.out.println(\"姓名:\" + name + \",薪资:\" + salary); }}// 子类:经理(继承自员工)class Manager extends Employee { private double bonus; // 奖金 // 子类构造器 public Manager(String name, double salary, double bonus) { super(name, salary); // 调用父类构造器(必须在第一行) this.bonus = bonus; } // 重写父类方法,并用super调用父类方法 @Override public void showInfo() { super.showInfo(); // 调用父类的showInfo() System.out.println(\"奖金:\" + bonus + \",总薪资:\" + (salary + bonus)); } // 子类方法:使用super访问父类属性 public void raiseSalary(double percent) { // 父类salary是protected,子类可通过super访问 salary = salary * (1 + percent / 100) + bonus; System.out.println(name + \"的薪资已调整为:\" + salary); }}// 测试类public class SuperDemo { public static void main(String[] args) { Manager manager = new Manager(\"张三\", 8000, 2000); manager.showInfo(); // 输出: // 姓名:张三,薪资:8000.0 // 奖金:2000.0,总薪资:10000.0 manager.raiseSalary(10); // 涨薪10% // 输出:张三的薪资已调整为:9800.0 }}

7.1.4 调用父类的构造方法

        子类构造器中,默认会隐式调用父类的无参构造器super());如果父类没有无参构造器,子类必须显式调用父类的有参构造器(super(参数)),否则编译报错。

流程图:构造器调用顺序

代码示例:父类构造器调用

// 父类:Personclass Person { private String name; private int age; // 父类有参构造器(注意:没有无参构造器) public Person(String name, int age) { this.name = name; this.age = age; System.out.println(\"Person构造器被调用:\" + name + \",\" + age + \"岁\"); }}// 子类:Student(继承自Person)class Student extends Person { private String school; // 学校 // 子类构造器:必须显式调用父类有参构造器 public Student(String name, int age, String school) { super(name, age); // 显式调用父类构造器(否则编译报错) this.school = school; System.out.println(\"Student构造器被调用:\" + school); }}// 测试类public class ConstructorCallDemo { public static void main(String[] args) { // 创建子类对象时,先调用父类构造器,再调用子类构造器 Student student = new Student(\"李四\", 18, \"北京大学\"); // 输出: // Person构造器被调用:李四,18岁 // Student构造器被调用:北京大学 }}

7.1 综合案例:动物继承体系

需求:设计动物继承体系,包含父类Animal子类DogCat,展示继承、方法覆盖和 super 关键字的综合应用。

类图

@startumlclass Animal { - String name + Animal(String name) + void eat() + void makeSound() + void sleep()}class Dog { + Dog(String name) + void makeSound() + void fetch()}class Cat { + Cat(String name) + void makeSound() + void climbTree()}Animal <|-- DogAnimal <|-- Cat@enduml

完整代码

// 父类:动物class Animal { protected String name; // 名字 // 父类构造器 public Animal(String name) { this.name = name; System.out.println(\"Animal构造器:\" + name); } // 吃东西(通用实现) public void eat() { System.out.println(name + \"在吃东西\"); } // 发出声音(父类默认实现) public void makeSound() { System.out.println(name + \"发出声音\"); } // 睡觉(通用实现) public void sleep() { System.out.println(name + \"在睡觉\"); }}// 子类:狗class Dog extends Animal { // 子类构造器 public Dog(String name) { super(name); // 调用父类构造器 System.out.println(\"Dog构造器:\" + name); } // 重写:狗的叫声 @Override public void makeSound() { System.out.println(name + \"汪汪叫\"); } // 子类特有方法:捡东西 public void fetch() { System.out.println(name + \"在捡球\"); super.eat(); // 调用父类的eat()方法 }}// 子类:猫class Cat extends Animal { // 子类构造器 public Cat(String name) { super(name); // 调用父类构造器 System.out.println(\"Cat构造器:\" + name); } // 重写:猫的叫声 @Override public void makeSound() { System.out.println(name + \"喵喵叫\"); } // 子类特有方法:爬树 public void climbTree() { System.out.println(name + \"在爬树\"); }}// 测试类public class AnimalInheritanceDemo { public static void main(String[] args) { System.out.println(\"===== 创建Dog对象 =====\"); Dog dog = new Dog(\"旺财\"); dog.eat(); // 继承父类方法 dog.makeSound();// 调用重写的方法 dog.sleep(); // 继承父类方法 dog.fetch(); // 子类特有方法 System.out.println(\"\\n===== 创建Cat对象 =====\"); Cat cat = new Cat(\"咪咪\"); cat.eat(); // 继承父类方法 cat.makeSound();// 调用重写的方法 cat.sleep(); // 继承父类方法 cat.climbTree();// 子类特有方法 }}

运行结果

7.2 封装性与访问修饰符

        封装是将数据和操作数据的方法捆绑在一起,并通过访问修饰符控制外部访问权限,实现 \"数据隐藏\"。

7.2.1 类的访问权限

Java 中类的访问权限只有两种:

  • public:公开类,可被所有包中的类访问
  • 默认权限(无修饰符):包内可见,仅同一包中的类可访问

规则

  • 一个 Java 源文件中最多有一个public,且文件名必须与public类名相同
  • 若类为public,其包路径需与文件夹结构一致

代码示例:类访问权限

// 文件:com/example/PublicClass.java(public类)package com.example;public class PublicClass { public void publicMethod() { System.out.println(\"public类的public方法\"); }}// 文件:com/example/DefaultClass.java(默认权限类)package com.example;class DefaultClass { // 无访问修饰符,默认权限 public void defaultClassMethod() { System.out.println(\"默认类的public方法\"); }}// 文件:com/other/TestClass.java(不同包的测试类)package com.other;import com.example.PublicClass;// import com.example.DefaultClass; // 编译报错:DefaultClass是默认权限,不同包不可访问public class TestClass { public static void main(String[] args) { PublicClass publicObj = new PublicClass(); publicObj.publicMethod(); // 正常访问:public类可跨包访问 // DefaultClass defaultObj = new DefaultClass(); // 编译报错:无法访问默认权限类 }}

7.2.2 类成员的访问权限

类成员(属性和方法)有 4 种访问权限,权限从大到小为:

修饰符 本类 同包类 不同包子类 其他类 public ✔️ ✔️ ✔️ ✔️ protected ✔️ ✔️ ✔️ ❌ 默认 ✔️ ✔️ ❌ ❌ private ✔️ ❌ ❌ ❌

最佳实践

  • 属性通常用private修饰,通过publicgetter/setter方法访问
  • 方法根据需要设置权限,对外暴露的接口用public,内部工具方法用private
  • 父子类共享的方法 / 属性用protected

代码示例:成员访问权限

// 父类:com/example/Parent.javapackage com.example;public class Parent { public String publicField = \"public属性\"; protected String protectedField = \"protected属性\"; String defaultField = \"default属性\"; // 默认权限 private String privateField = \"private属性\"; public void publicMethod() { System.out.println(\"public方法:\" + privateField); // 本类可访问private } protected void protectedMethod() { System.out.println(\"protected方法\"); } void defaultMethod() { System.out.println(\"default方法\"); } private void privateMethod() { System.out.println(\"private方法\"); }}// 同包子类:com/example/ChildSamePackage.javapackage com.example;public class ChildSamePackage extends Parent { public void accessParent() { System.out.println(publicField); // ✔️ public System.out.println(protectedField); // ✔️ protected System.out.println(defaultField); // ✔️ 同包默认权限 // System.out.println(privateField); // ❌ 不可访问private publicMethod(); // ✔️ protectedMethod(); // ✔️ defaultMethod(); // ✔️ 同包 // privateMethod(); // ❌ }}// 不同包子类:com/other/ChildDifferentPackage.javapackage com.other;import com.example.Parent;public class ChildDifferentPackage extends Parent { public void accessParent() { System.out.println(publicField); // ✔️ public System.out.println(protectedField); // ✔️ protected(子类) // System.out.println(defaultField); // ❌ 不同包默认权限不可访问 // System.out.println(privateField); // ❌ publicMethod(); // ✔️ protectedMethod(); // ✔️ 子类可访问 // defaultMethod(); // ❌ 不同包默认方法不可访问 // privateMethod(); // ❌ }}// 不同包非子类:com/other/OtherClass.javapackage com.other;import com.example.Parent;public class OtherClass { public void accessParent() { Parent parent = new Parent(); System.out.println(parent.publicField); // ✔️ public // System.out.println(parent.protectedField); // ❌ 非子类不可访问protected // System.out.println(parent.defaultField); // ❌ 不同包默认不可访问 // System.out.println(parent.privateField); // ❌ parent.publicMethod(); // ✔️ // parent.protectedMethod(); // ❌ 非子类不可访问 // parent.defaultMethod(); // ❌ // parent.privateMethod(); // ❌ }}

7.2 综合案例:封装与访问控制

需求:设计一个User类,通过访问修饰符实现封装,提供安全的属性访问方式。

完整代码

package com.example.encapsulation;// 用户类(封装示例)public class User { // 属性私有化(private) private String username; // 用户名 private String password; // 密码 private int age; // 年龄 // 无参构造器 public User() {} // 有参构造器 public User(String username, String password, int age) { this.username = username; this.password = password; this.age = age; } // 用户名的getter(public,对外提供读取权限) public String getUsername() { return username; } // 密码的getter(仅返回脱敏后的密码) public String getPasswordMasked() { if (password == null || password.length() <= 2) { return \"***\"; } return password.substring(0, 2) + \"***\"; // 前2位显示,其余脱敏 } // 密码的setter(提供修改权限,带简单验证) public void setPassword(String password) { if (password == null || password.length() < 6) { throw new IllegalArgumentException(\"密码长度不能少于6位\"); } this.password = password; } // 年龄的getter public int getAge() { return age; } // 年龄的setter(带验证逻辑) public void setAge(int age) { if (age  150) { throw new IllegalArgumentException(\"年龄必须在0-150之间\"); } this.age = age; } // 公开方法:用户登录 public boolean login(String inputPassword) { return password.equals(inputPassword); // 内部可访问private属性 }}// 测试类public class EncapsulationDemo { public static void main(String[] args) { User user = new User(\"zhangsan\", \"123456\", 25); // 访问用户名(通过getter) System.out.println(\"用户名:\" + user.getUsername()); // 输出:用户名:zhangsan // 访问脱敏密码 System.out.println(\"密码(脱敏):\" + user.getPasswordMasked()); // 输出:密码(脱敏):12*** // 测试年龄设置 user.setAge(30); System.out.println(\"年龄:\" + user.getAge()); // 输出:年龄:30 // 测试密码设置(合法) user.setPassword(\"newpass123\"); System.out.println(\"修改密码后(脱敏):\" + user.getPasswordMasked()); // 输出:ne*** // 测试登录 boolean loginSuccess = user.login(\"newpass123\"); System.out.println(\"登录成功?\" + loginSuccess); // 输出:true // 测试非法年龄(会抛出异常) try { user.setAge(200); } catch (IllegalArgumentException e) { System.out.println(\"年龄设置错误:\" + e.getMessage()); // 输出:年龄必须在0-150之间 } }}

7.3 防止类扩展和方法覆盖

final关键字用于限制类、方法或变量的修改,实现 \"不可变\" 特性。

7.3.1 final 修饰类

final修饰的类不能被继承(最终类),确保类的功能不被修改。

典型应用

  • JDK 中的StringInteger等类都是final
  • 工具类通常设计为final(如java.util.Math

代码示例:final 类

// final类:不能被继承final class FinalClass { public void show() { System.out.println(\"这是final类的方法\"); }}// 尝试继承final类(编译报错)// class SubClass extends FinalClass { // 错误:无法从最终类FinalClass继承// @Override// public void show() {// System.out.println(\"尝试覆盖final类的方法\");// }// }// 测试类public class FinalClassDemo { public static void main(String[] args) { FinalClass obj = new FinalClass(); obj.show(); // 输出:这是final类的方法 }}

7.3.2 final 修饰方法

final修饰的方法不能被子类覆盖,但类可以被继承。

应用场景

  • 确保核心方法的实现不被修改
  • 提升性能(JVM 可能对 final 方法进行优化)

代码示例:final 方法

// 父类:包含final方法class ParentWithFinalMethod { // final方法:不能被覆盖 public final void finalMethod() { System.out.println(\"这是final方法,不能被覆盖\"); } // 普通方法:可以被覆盖 public void normalMethod() { System.out.println(\"这是普通方法,可以被覆盖\"); }}// 子类class ChildWithFinalMethod extends ParentWithFinalMethod { // 尝试覆盖final方法(编译报错) // @Override // public void finalMethod() { // 错误:final方法不能被覆盖 // System.out.println(\"尝试覆盖final方法\"); // } // 覆盖普通方法(合法) @Override public void normalMethod() { System.out.println(\"子类覆盖了普通方法\"); }}// 测试类public class FinalMethodDemo { public static void main(String[] args) { ChildWithFinalMethod child = new ChildWithFinalMethod(); child.finalMethod(); // 输出:这是final方法,不能被覆盖 child.normalMethod(); // 输出:子类覆盖了普通方法 }}

7.3.3 final 修饰变量

final修饰的变量只能被赋值一次(常量),赋值后不可修改。

特性

  • 局部变量:声明时或构造器中赋值
  • 成员变量:声明时、构造块或构造器中赋值(必须保证创建对象时已初始化)
  • 引用类型变量:引用地址不可变,但对象内容可修改

代码示例:final 变量

public class FinalVariableDemo { // 成员常量:声明时赋值 public static final double PI = 3.14159; // 静态常量(通常全大写) private final String name; // 实例常量 // 构造块中初始化final变量(可选) { // name = \"默认名称\"; // 若此处赋值,构造器中不可再赋值 } // 构造器中初始化final变量 public FinalVariableDemo(String name) { this.name = name; // 必须赋值,否则编译报错 } public void showFinalVars() { // 局部final变量 final int MAX_COUNT; MAX_COUNT = 100; // 第一次赋值(合法) // MAX_COUNT = 200; // 错误:final变量不能重复赋值 System.out.println(\"PI:\" + PI); System.out.println(\"name:\" + name); System.out.println(\"MAX_COUNT:\" + MAX_COUNT); } public void modifyFinalObject() { // final引用类型变量 final StringBuilder sb = new StringBuilder(\"final引用\"); sb.append(\",但内容可修改\"); // 合法:对象内容可改 System.out.println(sb.toString()); // 输出:final引用,但内容可修改 // sb = new StringBuilder(\"新对象\"); // 错误:引用地址不可改 } public static void main(String[] args) { FinalVariableDemo demo = new FinalVariableDemo(\"测试\"); demo.showFinalVars(); demo.modifyFinalObject(); }}

7.4 抽象类

        抽象类(abstract class)是包含抽象方法的类,它不能被实例化只能作为父类被继承。抽象类用于定义通用模板,强制子类实现特定方法。

核心特性

  • abstract关键字修饰
  • 可包含抽象方法(无实现的方法)和具体方法(有实现)
  • 子类必须实现所有抽象方法,否则子类也必须是抽象类
  • 不能用final修饰(抽象类必须能被继承)

类图:抽象类与子类关系

@startumlabstract class Shape { + abstract double calculateArea() + abstract double calculatePerimeter() + void printInfo()}class Circle { - double radius + Circle(double radius) + double calculateArea() + double calculatePerimeter()}class Rectangle { - double length - double width + Rectangle(double length, double width) + double calculateArea() + double calculatePerimeter()}Shape <|-- CircleShape <|-- Rectangle@enduml

代码示例:抽象类应用

// 抽象类:形状(定义通用模板)abstract class Shape { // 抽象方法:计算面积(无实现,由子类实现) public abstract double calculateArea(); // 抽象方法:计算周长 public abstract double calculatePerimeter(); // 具体方法:打印形状信息(已有实现) public void printInfo() { System.out.println(\"面积:\" + calculateArea() + \",周长:\" + calculatePerimeter()); }}// 子类:圆形(必须实现所有抽象方法)class Circle extends Shape { private double radius; // 半径 public Circle(double radius) { this.radius = radius; } // 实现抽象方法:计算面积 @Override public double calculateArea() { return Math.PI * radius * radius; } // 实现抽象方法:计算周长 @Override public double calculatePerimeter() { return 2 * Math.PI * radius; }}// 子类:矩形class Rectangle extends Shape { private double length; // 长 private double width; // 宽 public Rectangle(double length, double width) { this.length = length; this.width = width; } @Override public double calculateArea() { return length * width; } @Override public double calculatePerimeter() { return 2 * (length + width); }}// 测试类public class AbstractClassDemo { public static void main(String[] args) { // 抽象类不能实例化 // Shape shape = new Shape(); // 错误:Shape是抽象的,无法实例化 // 创建子类对象,用父类引用接收 Shape circle = new Circle(5); System.out.println(\"圆形信息:\"); circle.printInfo(); // 调用抽象类的具体方法,实际执行子类实现 Shape rectangle = new Rectangle(4, 6); System.out.println(\"\\n矩形信息:\"); rectangle.printInfo(); }}

运行结果

7.5 对象转换与多态

        对象转换和多态是实现灵活编程的关键,它们允许我们用统一的方式处理不同类型的对象

7.5.1 对象转换

对象转换分为向上转型向下转型

  • 向上转型(自动转换):子类对象 → 父类引用(Parent p = new Child();
  • 向下转型(强制转换):父类引用 → 子类对象(Child c = (Child)p;,需确保安全性)

流程图:对象转换流程

代码示例:对象转换

// 父类:Animalclass Animal { public void eat() { System.out.println(\"动物吃东西\"); }}// 子类:Dogclass Dog extends Animal { @Override public void eat() { System.out.println(\"狗吃骨头\"); } // 子类特有方法 public void bark() { System.out.println(\"狗汪汪叫\"); }}// 子类:Catclass Cat extends Animal { @Override public void eat() { System.out.println(\"猫吃鱼\"); } // 子类特有方法 public void meow() { System.out.println(\"猫喵喵叫\"); }}// 测试类public class ObjectCastDemo { public static void main(String[] args) { // 1. 向上转型(自动) Animal animal = new Dog(); // animal引用指向Dog对象 animal.eat(); // 输出:狗吃骨头(多态) // 2. 尝试直接调用子类特有方法(编译报错) // animal.bark(); // 错误:Animal类没有bark()方法 // 3. 向下转型(强制) if (animal instanceof Dog) { // 先判断类型(安全) Dog dog = (Dog) animal; // 强制转换 dog.bark(); // 输出:狗汪汪叫(调用子类特有方法) } // 4. 错误的向下转型(运行时异常) Animal animal2 = new Cat(); // Dog dog2 = (Dog) animal2; // 编译通过,但运行时抛出ClassCastException }}

7.5.2 instanceof 运算符

instanceof用于判断对象的实际类型返回boolean,语法:对象 instanceof 类型

用途

  • 向下转型前检查类型,避免ClassCastException
  • 判断对象是否属于某个类或其子类

代码示例:instanceof 使用

// 复用上面的Animal、Dog、Cat类public class InstanceOfDemo { public static void main(String[] args) { Animal animal = new Dog(); // 判断animal是否是Dog类型 System.out.println(animal instanceof Dog); // true // 判断animal是否是Animal类型(父类) System.out.println(animal instanceof Animal); // true // 判断animal是否是Cat类型 System.out.println(animal instanceof Cat); // false // 空对象的instanceof判断 Animal nullAnimal = null; System.out.println(nullAnimal instanceof Animal); // false(空对象返回false) // 安全的向下转型 if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.bark(); // 安全调用 } }}

7.5.3 多态与动态绑定

        多态(Polymorphism)指同一操作作用于不同对象,产生不同结果。表现为:父类引用指向子类对象,调用方法时实际执行子类的实现。

        动态绑定(Dynamic Binding):程序运行时,JVM 根据对象的实际类型确定调用哪个方法的过程(而非引用类型)。

多态的条件

  1. 存在继承关系
  2. 子类覆盖父类方法
  3. 父类引用指向子类对象

代码示例:多态与动态绑定

// 父类:Shapeclass Shape { public void draw() { System.out.println(\"绘制形状\"); }}// 子类:Circleclass Circle extends Shape { @Override public void draw() { System.out.println(\"绘制圆形\"); }}// 子类:Rectangleclass Rectangle extends Shape { @Override public void draw() { System.out.println(\"绘制矩形\"); }}// 工具类:绘图工具(多态应用)class DrawingTool { // 接收父类引用,实现通用绘图方法 public static void drawShape(Shape shape) { shape.draw(); // 动态绑定:调用实际类型的draw() }}// 测试类public class PolymorphismDemo { public static void main(String[] args) { // 父类引用指向不同子类对象 Shape circle = new Circle(); Shape rectangle = new Rectangle(); // 多态:同一方法调用,不同结果 DrawingTool.drawShape(circle); // 输出:绘制圆形 DrawingTool.drawShape(rectangle); // 输出:绘制矩形 }}

运行结果

7.5 综合案例:多态计算器

需求:设计一个计算器,支持整数、小数和字符串拼接的加法运算,用多态实现统一调用接口。

完整代码

// 抽象父类:计算器操作abstract class Calculator { // 抽象方法:计算(子类实现不同类型的计算) public abstract Object calculate(Object a, Object b);}// 子类:整数计算器class IntegerCalculator extends Calculator { @Override public Object calculate(Object a, Object b) { // 类型检查 if (!(a instanceof Integer) || !(b instanceof Integer)) { throw new IllegalArgumentException(\"整数计算器只支持Integer类型\"); } // 强转并计算 int num1 = (Integer) a; int num2 = (Integer) b; return num1 + num2; }}// 子类:小数计算器class DoubleCalculator extends Calculator { @Override public Object calculate(Object a, Object b) { // 支持Integer和Double混合计算 double num1 = (a instanceof Integer) ? (Integer) a : (Double) a; double num2 = (b instanceof Integer) ? (Integer) b : (Double) b; return num1 + num2; }}// 子类:字符串计算器(拼接)class StringCalculator extends Calculator { @Override public Object calculate(Object a, Object b) { // 任何类型都转为字符串拼接 return a.toString() + b.toString(); }}// 测试类public class PolymorphismCalculatorDemo { public static void main(String[] args) { // 创建不同计算器(多态数组) Calculator[] calculators = { new IntegerCalculator(), new DoubleCalculator(), new StringCalculator() }; // 测试整数计算 System.out.println(\"整数计算:10 + 20 = \" + calculators[0].calculate(10, 20)); // 测试小数计算 System.out.println(\"小数计算:3.5 + 4.8 = \" + calculators[1].calculate(3.5, 4.8)); System.out.println(\"混和计算:5 + 3.2 = \" + calculators[1].calculate(5, 3.2)); // 测试字符串拼接 System.out.println(\"字符串拼接:\\\"Hello\\\" + \\\"World\\\" = \" + calculators[2].calculate(\"Hello\", \"World\")); System.out.println(\"对象拼接:123 + true = \" + calculators[2].calculate(123, true)); }}

运行结果

7.6 小结

本章重点讲解了 Java 的继承与多态特性,核心知识点包括:

  1. 继承:通过extends实现代码复用,子类继承父类的属性和方法,可通过super访问父类资源
  2. 方法覆盖:子类重写父类方法,需遵循方法签名、权限等规则,用@Override注解标识
  3. 封装与访问修饰符:通过public/protected/default/private控制访问权限,实现数据安全
  4. final 关键字:用于修饰类(不可继承)、方法(不可覆盖)、变量(常量)
  5. 抽象类:含抽象方法的类,强制子类实现特定方法,作为通用模板使用
  6. 对象转换:向上转型(自动)和向下转型(强制),instanceof用于类型检查
  7. 多态与动态绑定:父类引用指向子类对象,运行时调用实际类型的方法,实现灵活编程

编程练习

练习 1:继承与方法覆盖

        需求:设计Person类作为父类,包含nameage属性及introduce()方法;Student类继承Person,新增studentId属性,并重写introduce()方法;Teacher类继承Person,新增subject属性,并重写introduce()方法。

参考答案

// Person类class Person { protected String name; protected int age; public Person(String name, int age) { this.name = name; this.age = age; } public void introduce() { System.out.println(\"大家好,我叫\" + name + \",今年\" + age + \"岁\"); }}// Student类class Student extends Person { private String studentId; public Student(String name, int age, String studentId) { super(name, age); this.studentId = studentId; } @Override public void introduce() { System.out.println(\"大家好,我叫\" + name + \",今年\" + age + \"岁,学号是\" + studentId); }}// Teacher类class Teacher extends Person { private String subject; public Teacher(String name, int age, String subject) { super(name, age); this.subject = subject; } @Override public void introduce() { System.out.println(\"大家好,我叫\" + name + \",今年\" + age + \"岁,教\" + subject); }}// 测试类public class PersonDemo { public static void main(String[] args) { Person student = new Student(\"小明\", 18, \"2023001\"); Person teacher = new Teacher(\"李老师\", 35, \"数学\"); student.introduce(); // 输出:大家好,我叫小明,今年18岁,学号是2023001 teacher.introduce(); // 输出:大家好,我叫李老师,今年35岁,教数学 }}

练习 2:多态应用

        需求:设计抽象类Vehicle,包含抽象方法run()CarBicycleTrain类继承Vehicle并实现run();创建工具类TrafficTool,用多态方法start(Vehicle vehicle)调用不同交通工具的运行方法。

参考答案

// 抽象类:交通工具abstract class Vehicle { public abstract void run();}// 汽车class Car extends Vehicle { @Override public void run() { System.out.println(\"汽车在公路上行驶\"); }}// 自行车class Bicycle extends Vehicle { @Override public void run() { System.out.println(\"自行车在自行车道骑行\"); }}// 火车class Train extends Vehicle { @Override public void run() { System.out.println(\"火车在铁轨上行驶\"); }}// 工具类class TrafficTool { public static void start(Vehicle vehicle) { System.out.print(\"交通工具启动:\"); vehicle.run(); // 多态调用 }}// 测试类public class VehicleDemo { public static void main(String[] args) { Vehicle car = new Car(); Vehicle bicycle = new Bicycle(); Vehicle train = new Train(); TrafficTool.start(car); // 输出:交通工具启动:汽车在公路上行驶 TrafficTool.start(bicycle); // 输出:交通工具启动:自行车在自行车道骑行 TrafficTool.start(train); // 输出:交通工具启动:火车在铁轨上行驶 }}

练习 3:抽象类与 final 综合应用

        需求:设计抽象类BankAccount(银行账户),包含抽象方法calculateInterest()(计算利息);SavingsAccount(储蓄账户)和CurrentAccount(活期账户)继承并实现利息计算;用final修饰BankAccountaccountNumber属性(账号不可修改)。

参考答案

// 抽象类:银行账户abstract class BankAccount { // 账号:final修饰,不可修改 private final String accountNumber; protected double balance; // 余额 public BankAccount(String accountNumber, double balance) { this.accountNumber = accountNumber; this.balance = balance; } // 抽象方法:计算利息 public abstract double calculateInterest(); // 获取账号(只提供getter,无setter) public String getAccountNumber() { return accountNumber; } // 存款 public void deposit(double amount) { if (amount > 0) { balance += amount; System.out.println(\"存款\" + amount + \"元,当前余额:\" + balance); } } // 显示账户信息 public void showAccountInfo() { System.out.println(\"账号:\" + accountNumber + \",余额:\" + balance + \"元,利息:\" + calculateInterest() + \"元\"); }}// 储蓄账户(利息3%)class SavingsAccount extends BankAccount { public SavingsAccount(String accountNumber, double balance) { super(accountNumber, balance); } @Override public double calculateInterest() { return balance * 0.03; // 年利率3% }}// 活期账户(利息0.3%)class CurrentAccount extends BankAccount { public CurrentAccount(String accountNumber, double balance) { super(accountNumber, balance); } @Override public double calculateInterest() { return balance * 0.003; // 年利率0.3% }}// 测试类public class BankAccountDemo { public static void main(String[] args) { BankAccount savings = new SavingsAccount(\"SA2023001\", 10000); BankAccount current = new CurrentAccount(\"CA2023001\", 5000); savings.deposit(2000); current.deposit(1000); savings.showAccountInfo(); // 输出:账号:SA2023001,余额:12000.0元,利息:360.0元 current.showAccountInfo(); // 输出:账号:CA2023001,余额:6000.0元,利息:18.0元 }}

        希望通过本章内容,你能彻底掌握 Java 继承与多态的核心概念和实践技巧。如果有任何问题或代码运行问题,欢迎在评论区交流!