Java 继承与多态详解:以 Spring Resource 体系为例

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+)模拟多继承

关键规则(面试高频):

  1. 构造器不继承:子类必须显式调用 super() 来初始化父类(默认第一行隐式调用无参 super)。
  2. 访问修饰符:protected 成员在子类可见;private 不继承。
  3. 方法重写(Override):子类方法签名(名称+参数)相同,返回类型兼容,访问修饰符不能更严格,异常不能更宽。
  4. 字段隐藏:子类同名字段会“隐藏”父类字段(用 super.field 访问父类)。
  5. final 关键字:final 类不能被继承;final 方法不能被重写。
  6. 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)基础详解

定义:多态指“多种形态”,允许不同类的对象对同一消息做出不同响应。核心是通过父类/接口引用指向子类/实现类对象,在运行时动态决定调用哪个方法。

多态的两种形式

  1. 编译时多态(静态多态):方法重载(Overload)——同一类中方法名相同,参数不同。
  2. 运行时多态(动态多态):方法重写(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?

随时告诉我,我们继续~ 🚀

文章已创建 4357

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部