引言
在 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 的继承关系
@startuml
class 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),也称为方法重写。
方法覆盖的规则:
- 方法名、参数列表必须与父类完全相同
- 返回值类型:父类返回值为
T
,子类可以是T
或T的子类
(协变返回) - 访问权限:子类方法权限不能低于父类(如父类
protected
,子类可protected
或public
) - 不能抛出比父类更多的 checked 异常
- 用
@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(参数)
),否则编译报错。
流程图:构造器调用顺序
代码示例:父类构造器调用
// 父类:Person
class 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
,子类Dog
和Cat
,展示继承、方法覆盖和 super 关键字的综合应用。
类图
@startuml
class 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 <|-- Dog
Animal <|-- 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
修饰,通过public
的getter/setter
方法访问 - 方法根据需要设置权限,对外暴露的接口用
public
,内部工具方法用private
- 父子类共享的方法 / 属性用
protected
代码示例:成员访问权限
// 父类:com/example/Parent.java
package 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.java
package 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.java
package 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.java
package 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 < 0 || 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 中的
String
、Integer
等类都是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
修饰(抽象类必须能被继承)
类图:抽象类与子类关系
@startuml
abstract 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 <|-- Circle
Shape <|-- 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;
,需确保安全性)
流程图:对象转换流程
代码示例:对象转换
// 父类:Animal
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
// 子类:Dog
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
// 子类特有方法
public void bark() {
System.out.println("狗汪汪叫");
}
}
// 子类:Cat
class 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 根据对象的实际类型确定调用哪个方法的过程(而非引用类型)。
多态的条件:
- 存在继承关系
- 子类覆盖父类方法
- 父类引用指向子类对象
代码示例:多态与动态绑定
// 父类:Shape
class Shape {
public void draw() {
System.out.println("绘制形状");
}
}
// 子类:Circle
class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
// 子类:Rectangle
class 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 的继承与多态特性,核心知识点包括:
- 继承:通过
extends
实现代码复用,子类继承父类的属性和方法,可通过super
访问父类资源 - 方法覆盖:子类重写父类方法,需遵循方法签名、权限等规则,用
@Override
注解标识 - 封装与访问修饰符:通过
public
/protected
/default
/private
控制访问权限,实现数据安全 - final 关键字:用于修饰类(不可继承)、方法(不可覆盖)、变量(常量)
- 抽象类:含抽象方法的类,强制子类实现特定方法,作为通用模板使用
- 对象转换:向上转型(自动)和向下转型(强制),
instanceof
用于类型检查 - 多态与动态绑定:父类引用指向子类对象,运行时调用实际类型的方法,实现灵活编程
编程练习
练习 1:继承与方法覆盖
需求:设计Person
类作为父类,包含name
和age
属性及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()
;Car
、Bicycle
、Train
类继承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
修饰BankAccount
的accountNumber
属性(账号不可修改)。
参考答案:
// 抽象类:银行账户
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 继承与多态的核心概念和实践技巧。如果有任何问题或代码运行问题,欢迎在评论区交流!