Java 继承与多态详解:以 Spring Resource 体系为例
重阳,你好!继承(Inheritance)和多态(Polymorphism)是 Java 面向对象编程(OOP)的两大核心支柱。它们让代码更灵活、可复用、可扩展,尤其在框架设计中(如 Spring)发挥巨大作用。今天我们从基础概念入手,逐步深入,然后用 Spring 的 Resource 体系(一个经典的接口 + 多实现设计)作为真实案例来剖析。整个讲解基于 JDK 21+ 的视角,但这些概念在 Java 1.x 就已确立,2026 年仍然是后端开发的基石。
1. 继承(Inheritance)基础详解
定义:继承允许一个类(子类/派生类)从另一个类(父类/基类)获取属性和方法。子类可以“继承”父类的非私有成员,同时可以添加新成员或重写(override)父类方法。
核心语法:
- 使用
extends关键字继承类(Java 只支持单继承)。 - 使用
implements关键字实现接口(支持多实现)。
继承的优缺点:
| 优点 | 缺点 | 最佳实践建议 |
|---|---|---|
| 代码复用(减少重复) | 紧耦合(子类依赖父类) | 优先用组合而非继承(Composition over Inheritance) |
| 层次化结构(易扩展) | 继承链过长导致脆弱基类 | 设计时让父类 abstract 或 final |
| 支持多态(见下文) | 不支持多继承(菱形问题) | 用接口 + 默认方法(JDK8+)模拟多继承 |
关键规则(面试高频):
- 构造器不继承:子类必须显式调用
super()来初始化父类(默认第一行隐式调用无参 super)。 - 访问修饰符:protected 成员在子类可见;private 不继承。
- 方法重写(Override):子类方法签名(名称+参数)相同,返回类型兼容,访问修饰符不能更严格,异常不能更宽。
- 字段隐藏:子类同名字段会“隐藏”父类字段(用
super.field访问父类)。 - final 关键字:final 类不能被继承;final 方法不能被重写。
- Object 是所有类的父类:默认继承 Object 的 equals、hashCode、toString 等。
示例代码(简单继承):
// 父类
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " 正在吃东西");
}
}
// 子类
public class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类构造器
}
@Override // 推荐加注解,编译期检查
public void eat() {
super.eat(); // 调用父类方法
System.out.println(name + " 喜欢吃骨头");
}
}
2. 多态(Polymorphism)基础详解
定义:多态指“多种形态”,允许不同类的对象对同一消息做出不同响应。核心是通过父类/接口引用指向子类/实现类对象,在运行时动态决定调用哪个方法。
多态的两种形式:
- 编译时多态(静态多态):方法重载(Overload)——同一类中方法名相同,参数不同。
- 运行时多态(动态多态):方法重写(Override)——通过继承/接口实现,在运行时根据实际对象类型决定调用。
多态实现机制(底层原理):
- 向上转型(Upcasting):子类对象赋给父类引用(自动)。如
Animal dog = new Dog(); - 向下转型(Downcasting):父类引用转回子类(需强制转换 + instanceof 检查)。如
Dog d = (Dog) animal; - 动态方法分派:JVM 通过虚方法表(vtable)在运行时查找实际类的方法实现。
多态的优缺点:
| 优点 | 缺点 | 最佳实践建议 |
|---|---|---|
| 接口编程(松耦合) | 类型转换风险(ClassCastException) | 总是用 instanceof 检查向下转型 |
| 易扩展(新增子类不改代码) | 性能略低(动态分派) | 优先用接口而非抽象类 |
| 符合开闭原则(OCP) | 调试复杂(运行时行为) | 用 @Override 注解避免错误重写 |
示例代码(运行时多态):
Animal animal = new Dog("旺财"); // 向上转型
animal.eat(); // 输出:旺财 正在吃东西 \n 旺财 喜欢吃骨头 (运行时调用 Dog 的 eat)
3. 以 Spring Resource 体系为例:继承与多态的实战应用
Spring Framework 的 Resource 接口(org.springframework.core.io.Resource)是一个完美的例子,展示了如何用继承和多态设计一个灵活的“资源加载”系统。Resource 表示各种资源(如 classpath 文件、URL、文件系统、字节数组),广泛用于 Spring 配置加载、静态资源处理等。
Resource 体系结构(继承 + 接口实现):
- 顶级接口:Resource 继承了 InputStreamSource 接口(提供 getInputStream() 方法)。
- 抽象类:AbstractResource 提供了默认实现(如 exists()、isReadable() 等),子类只需重写核心方法。
- 具体实现类:多个子类继承 AbstractResource 或直接实现 Resource 接口。
核心接口定义(简化):
public interface Resource extends InputStreamSource {
boolean exists(); // 是否存在
boolean isReadable(); // 是否可读
URL getURL() throws IOException; // 获取 URL
File getFile() throws IOException; // 获取 File
long contentLength() throws IOException; // 内容长度
String getDescription(); // 描述
// ... 其他方法
}
继承链示例:
- AbstractResource(抽象类)extends Object,实现 Resource 接口,提供默认方法实现。
- ClassPathResource extends AbstractFileResolvingResource(另一个抽象类),AbstractFileResolvingResource extends AbstractResource。
- 类似:FileSystemResource、UrlResource、ByteArrayResource 等。
这体现了继承的层次化:抽象类复用代码,具体类重写特定行为(如 ClassPathResource 重写 getInputStream() 从 classpath 加载)。
多态在 Spring 中的应用:
Spring 通过 Resource 接口引用不同实现,实现“一次编写,到处使用”。
示例代码(Spring Boot 项目中常见用法):
import org.springframework.core.io.Resource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.UrlResource;
public class ResourceDemo {
public static void main(String[] args) throws Exception {
// 多态:用接口引用不同子类对象
Resource resource;
// 1. ClassPathResource:从 classpath 加载
resource = new ClassPathResource("application.properties");
System.out.println("ClassPath: " + resource.getDescription()); // 输出:class path resource [application.properties]
System.out.println("Exists: " + resource.exists());
// 2. FileSystemResource:从文件系统加载
resource = new FileSystemResource("/path/to/file.txt"); // 运行时动态切换
System.out.println("FileSystem: " + resource.getDescription()); // 输出:file [/path/to/file.txt]
// 3. UrlResource:从 URL 加载
resource = new UrlResource("https://example.com/remote.txt");
System.out.println("Url: " + resource.getDescription()); // 输出:URL [https://example.com/remote.txt]
// 统一调用接口方法(多态体现)
InputStream is = resource.getInputStream(); // 无论哪种实现,都能获取输入流
// ... 读取内容
}
}
为什么这个设计体现了继承与多态的精髓?
- 继承:AbstractResource 提供了通用方法(如 contentLength() 默认实现基于 getInputStream()),子类继承并重写(如 UrlResource 重写 getURL() 返回 URL)。
- 多态:Spring 内部(如 ResourceLoader)用 Resource 接口接收任意实现,用户无需关心是文件还是 URL,就能统一处理(e.g.,
ResourceLoader.getResource("classpath:xx")返回对应子类)。 - 扩展性:想加新资源类型?只需实现 Resource 接口,重写方法,Spring 就能无缝集成(符合 OCP 原则)。
- 实际场景:在 Spring Boot 的 @Value(“${xx}”) 或 YamlPropertySourceLoader 中,都用 Resource 多态加载配置文件。
潜在坑点(面试常问):
- 向下转型风险:
((ClassPathResource) resource).getPath()可能抛 ClassCastException,如果 resource 是 UrlResource。 - 重写 equals/hashCode:Resource 实现类通常重写这些(继承自 Object),确保正确比较。
- 异常处理:getFile() 等方法可能抛 IOException(checked 异常),需处理。
重阳,继承和多态吃透后,你看 Spring 源码会觉得特别亲切!这个例子帮你理解了吗?
想继续深挖:
- 手写一个类似 Resource 的小框架?
- 继承 vs 组合的对比案例?
- 多态在 Spring IOC/AOP 中的应用?
- 还是有代码报错想让我帮 debug?
随时告诉我,我们继续~ 🚀