01.03 Java基础篇|面向对象核心与设计实践

01.03 Java基础篇|面向对象核心与设计实践

导读

  • 适用人群:具备 Java 语法基础,准备系统掌握 OOP 思维与设计原则的开发者。
  • 目标:结合原理、源码片段、伪代码与面试题,形成可直接发布的 OOP 系列文章。
  • 阅读建议:先通读核心知识架构,再按项目实践、模式案例与面试速记巩固。

核心知识架构

OOP 四大特性深度解析

1. 封装(Encapsulation)

核心思想:隐藏内部实现细节,通过公开 API 暴露行为

访问控制符详解

java 复制代码
public class BankAccount {
    // public: 任何地方可访问
    public String accountNumber;
    
    // protected: 同包或子类可访问
    protected double balance;
    
    // 包私有(default): 同包可访问
    String bankName;
    
    // private: 仅本类可访问
    private String password;
    
    // 通过公开方法控制访问
    public double getBalance() {
        return balance;
    }
    
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            logTransaction("deposit", amount);
        }
    }
    
    private void logTransaction(String type, double amount) {
        // 内部实现细节,外部不可见
    }
}

不可变对象设计

java 复制代码
// 完全不可变类
public final class ImmutablePoint {
    private final double x;
    private final double y;
    
    public ImmutablePoint(double x, double y) {
        this.x = x;
        this.y = y;
    }
    
    // 只提供 getter,不提供 setter
    public double getX() { return x; }
    public double getY() { return y; }
    
    // 提供创建新对象的方法(函数式风格)
    public ImmutablePoint withX(double newX) {
        return new ImmutablePoint(newX, this.y);
    }
    
    public ImmutablePoint withY(double newY) {
        return new ImmutablePoint(this.x, newY);
    }
}

final 关键字深度解析

final 的三种用法

  1. final 类:不能被继承

    java 复制代码
    public final class ImmutablePoint {
        // 不能被继承
    }
    
    // 编译错误
    // public class MutablePoint extends ImmutablePoint { }
  2. final 方法:不能被重写

    java 复制代码
    public class Parent {
        public final void method() {
            // 子类不能重写
        }
    }
    
    public class Child extends Parent {
        // 编译错误:不能重写 final 方法
        // @Override
        // public void method() { }
    }
  3. final 变量:只能赋值一次

    java 复制代码
    // 基本类型:值不能改变
    final int x = 10;
    // x = 20;  // 编译错误
    
    // 引用类型:引用不能改变,但对象内容可以改变
    final List<String> list = new ArrayList<>();
    list.add("item");  // 可以修改对象内容
    // list = new ArrayList<>();  // 编译错误:不能改变引用

为什么不可变类需要 final 关键字?

虽然一个类即使没有 final 关键字,只要没有 setter 方法,从外部看似乎是不可变的,但使用 final 关键字有以下重要原因:

1. 防止子类破坏不可变性

java 复制代码
// 没有 final 的"不可变"类(实际上不安全)
public class Point {
    private double x;
    private double y;
    
    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
    
    public double getX() { return x; }
    public double getY() { return y; }
    // 没有 setter
}

// 子类可以破坏不可变性
public class MutablePoint extends Point {
    public MutablePoint(double x, double y) {
        super(x, y);
    }
    
    // 通过反射或其他方式修改父类字段
    public void setX(double x) {
        // 使用反射修改父类的 private 字段
        try {
            Field field = Point.class.getDeclaredField("x");
            field.setAccessible(true);
            field.set(this, x);
        } catch (Exception e) {
            // ...
        }
    }
}

2. 防止子类添加可变状态

java 复制代码
// 没有 final 的类
public class ImmutableUser {
    private final String name;
    
    public ImmutableUser(String name) {
        this.name = name;
    }
    
    public String getName() { return name; }
}

// 子类可以添加可变字段
public class MutableUser extends ImmutableUser {
    private String email;  // 可变字段
    
    public MutableUser(String name, String email) {
        super(name);
        this.email = email;
    }
    
    public void setEmail(String email) {
        this.email = email;  // 破坏了不可变性
    }
}

// 使用示例
ImmutableUser user = new MutableUser("Alice", "alice@example.com");
// 虽然类型是 ImmutableUser,但实际对象是可变的
((MutableUser) user).setEmail("new@example.com");

3. 性能优化

java 复制代码
// final 类的方法调用可以被内联优化
public final class ImmutablePoint {
    public double getX() { return x; }
}

// JVM 可以内联 final 类的方法,提升性能
// 因为知道不会有子类重写方法

4. 线程安全保证

java 复制代码
// final 类 + final 字段 = 完全不可变 = 线程安全
public final class ImmutablePoint {
    private final double x;  // final 字段
    private final double y;  // final 字段
    
    // 无需同步即可在多线程间安全共享
}

// 没有 final 的类,即使字段是 final,子类仍可能破坏线程安全
public class Point {
    private final double x;  // final 字段
    // 但子类可能添加非 final 字段,破坏线程安全
}

5. 设计意图明确

java 复制代码
// final 关键字明确表达设计意图:这个类不应该被继承
public final class ImmutablePoint {
    // 设计者明确表示:这是最终版本,不要继承
}

// 没有 final,设计意图不明确
public class Point {
    // 使用者不知道是否可以继承
}

完整的不可变类设计原则

java 复制代码
// 1. 类声明为 final
public final class ImmutableUser {
    // 2. 所有字段声明为 final
    private final String name;
    private final int age;
    private final List<String> tags;  // 集合也需要不可变
    
    // 3. 构造器初始化所有字段
    public ImmutableUser(String name, int age, List<String> tags) {
        this.name = Objects.requireNonNull(name);
        this.age = age;
        // 4. 防御性拷贝(防止外部修改)
        this.tags = Collections.unmodifiableList(new ArrayList<>(tags));
    }
    
    // 5. 只提供 getter,不提供 setter
    public String getName() { return name; }
    public int getAge() { return age; }
    public List<String> getTags() {
        // 6. 返回不可变视图
        return tags;  // 已经是不可变列表
    }
    
    // 7. 提供 withXxx 方法创建新对象(函数式风格)
    public ImmutableUser withAge(int newAge) {
        return new ImmutableUser(this.name, newAge, this.tags);
    }
}

对比示例:有 final vs 无 final

java 复制代码
// 方案1:没有 final(不安全)
public class UnsafeImmutable {
    private final String value;
    
    public UnsafeImmutable(String value) {
        this.value = value;
    }
    
    public String getValue() { return value; }
}

// 子类可以破坏不可变性
public class MutableSubclass extends UnsafeImmutable {
    private String mutableValue;  // 添加可变字段
    
    public MutableSubclass(String value) {
        super(value);
        this.mutableValue = value;
    }
    
    public void setMutableValue(String v) {
        this.mutableValue = v;  // 破坏了不可变性
    }
}

// 方案2:使用 final(安全)
public final class SafeImmutable {
    private final String value;
    
    public SafeImmutable(String value) {
        this.value = value;
    }
    
    public String getValue() { return value; }
}

// 编译错误:不能继承 final 类
// public class MutableSubclass extends SafeImmutable { }

总结:为什么需要 final

原因 说明 示例
防止继承破坏 子类可能通过反射或其他方式修改父类字段 子类使用反射修改 private 字段
防止添加可变状态 子类可以添加可变字段,破坏不可变性 子类添加 setter 方法
性能优化 JVM 可以内联 final 类的方法 方法调用优化
线程安全 final 类 + final 字段 = 完全不可变 = 线程安全 无需同步即可共享
设计意图 明确表达类不应该被继承 代码可读性
2. 继承(Inheritance)

单继承限制

java 复制代码
// Java 只支持单继承
public class Animal {
    protected String name;
    
    public void eat() {
        System.out.println(name + " is eating");
    }
}

// 子类继承父类
public class Dog extends Animal {
    public void bark() {
        System.out.println(name + " is barking");
    }
}

// 编译错误:不能多继承
// public class Dog extends Animal, Pet { }

方法重写规则

java 复制代码
public class Parent {
    // 父类方法
    public Number getValue() {
        return 1;
    }
}

public class Child extends Parent {
    // 重写:返回类型可以是父类返回类型的子类(协变返回类型)
    @Override
    public Integer getValue() {
        return 2;
    }
}

super 关键字使用

java 复制代码
public class Parent {
    protected String name;
    
    public Parent(String name) {
        this.name = name;
    }
}

public class Child extends Parent {
    private int age;
    
    public Child(String name, int age) {
        super(name);  // 必须调用父类构造器
        this.age = age;
    }
    
    @Override
    public String toString() {
        return super.toString() + ", age=" + age;  // 调用父类方法
    }
}
3. 多态(Polymorphism)

编译期多态(方法重载)

java 复制代码
public class Calculator {
    // 方法重载:方法名相同,参数不同
    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
    
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

// 调用时根据参数类型选择方法
Calculator calc = new Calculator();
calc.add(1, 2);        // 调用 int add(int, int)
calc.add(1.0, 2.0);   // 调用 double add(double, double)

运行时多态(方法重写)

java 复制代码
// 父类引用指向子类对象
Animal animal = new Dog();
animal.eat();  // 运行时调用 Dog 的 eat 方法

// 方法表(Method Table)机制
// JVM 为每个类维护一个方法表,包含所有方法的入口地址
// 调用时根据对象的实际类型查找方法表

多态的实现原理(JVM 层面)

java 复制代码
// 编译后的字节码(简化)
// invokevirtual 指令:根据对象的实际类型调用方法
// 1. 从对象头获取类信息
// 2. 在类的方法表中查找方法
// 3. 调用找到的方法
4. 抽象(Abstraction)

抽象类 vs 接口演进

特性 抽象类 接口(JDK 8+)
字段 可以有实例字段 只能是常量(public static final)
构造器 可以有 不能有
方法实现 可以有 默认方法(default)
继承 单继承 多实现
静态方法 可以有 JDK 8+ 支持
私有方法 可以有 JDK 9+ 支持

接口默认方法(JDK 8+)

java 复制代码
public interface PaymentProcessor {
    // 抽象方法
    boolean processPayment(double amount);
    
    // 默认方法:提供默认实现
    default void validateAmount(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
    }
    
    // 静态方法
    static PaymentProcessor create(String type) {
        return switch (type) {
            case "credit" -> new CreditCardProcessor();
            case "paypal" -> new PayPalProcessor();
            default -> throw new IllegalArgumentException("Unknown type: " + type);
        };
    }
    
    // 私有方法(JDK 9+)
    private void logTransaction(double amount) {
        System.out.println("Processing payment: " + amount);
    }
}

SOLID 设计原则深度解析

S - 单一职责原则(Single Responsibility Principle)

核心思想:一个类应该只有一个引起它变化的原因

违反示例

java 复制代码
// 违反单一职责:User 类承担了太多职责
public class User {
    private String name;
    private String email;
    
    // 职责1:用户信息管理
    public void setName(String name) { this.name = name; }
    
    // 职责2:数据持久化
    public void save() {
        // 数据库操作
    }
    
    // 职责3:邮件发送
    public void sendEmail(String message) {
        // 邮件服务调用
    }
    
    // 职责4:数据验证
    public boolean validate() {
        // 验证逻辑
    }
}

重构后

java 复制代码
// 职责分离
public class User {
    private String name;
    private String email;
    // 只负责用户数据
}

public class UserRepository {
    public void save(User user) {
        // 数据持久化
    }
}

public class EmailService {
    public void sendEmail(User user, String message) {
        // 邮件发送
    }
}

public class UserValidator {
    public boolean validate(User user) {
        // 验证逻辑
    }
}
O - 开闭原则(Open-Closed Principle)

核心思想:对扩展开放,对修改关闭

违反示例

java 复制代码
// 违反开闭原则:添加新类型需要修改现有代码
public class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Rectangle) {
            Rectangle r = (Rectangle) shape;
            return r.width * r.height;
        } else if (shape instanceof Circle) {
            Circle c = (Circle) shape;
            return Math.PI * c.radius * c.radius;
        }
        // 添加新形状需要修改这里
        throw new IllegalArgumentException("Unknown shape");
    }
}

遵循开闭原则

java 复制代码
// 使用多态,扩展时无需修改现有代码
public interface Shape {
    double calculateArea();
}

public class Rectangle implements Shape {
    private double width;
    private double height;
    
    @Override
    public double calculateArea() {
        return width * height;
    }
}

public class Circle implements Shape {
    private double radius;
    
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

// 添加新形状只需实现接口,无需修改 AreaCalculator
public class Triangle implements Shape {
    private double base;
    private double height;
    
    @Override
    public double calculateArea() {
        return 0.5 * base * height;
    }
}
L - 里氏替换原则(Liskov Substitution Principle)

核心思想:子类对象应该能够替换父类对象,而不影响程序的正确性

里氏替换原则的数学定义

  • 如果对每个类型 T1 的对象 o1,都有类型 T2 的对象 o2,使得以 T1 定义的所有程序 P 在 o1 替换为 o2 时,P 的行为不变,那么 T2 是 T1 的子类型

违反示例详解

java 复制代码
// 违反里氏替换:子类改变了父类的行为
public class Rectangle {
    protected int width;
    protected int height;
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    public int getArea() {
        return width * height;
    }
    
    // 矩形的不变式(Invariant):width 和 height 可以独立设置
    // 预期行为:setWidth 只改变 width,setHeight 只改变 height
}

// Square 违反了里氏替换原则
public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width;  // ❌ 改变了父类的行为:同时修改了 height
    }
    
    @Override
    public void setHeight(int height) {
        this.width = height;   // ❌ 改变了父类的行为:同时修改了 width
        this.height = height;
    }
    
    // 正方形的不变式:width == height
    // 问题:为了满足正方形的不变式,破坏了矩形的行为约定
}

// 使用示例:违反里氏替换的具体场景
void testRectangle(Rectangle r) {
    // 测试场景1:独立设置宽高
    r.setWidth(5);
    r.setHeight(4);
    assert r.getArea() == 20;  // 预期:5 * 4 = 20
    
    // 如果传入 Square 对象:
    // setWidth(5) → width=5, height=5 (被强制设为5)
    // setHeight(4) → width=4, height=4 (被强制设为4)
    // getArea() = 4 * 4 = 16 ≠ 20 ❌ 断言失败!
}

// 测试场景2:先设置宽度,再设置高度
void resizeRectangle(Rectangle r, int newWidth, int newHeight) {
    r.setWidth(newWidth);   // 预期:只改变宽度
    // 中间可能有其他操作
    r.setHeight(newHeight); // 预期:只改变高度
    
    // 如果传入 Square:
    // setWidth(5) → width=5, height=5
    // setHeight(4) → width=4, height=4
    // 最终结果:width=4, height=4
    // 但调用者期望的是 width=5, height=4 ❌
}

// 测试场景3:使用多态
List<Rectangle> rectangles = Arrays.asList(
    new Rectangle(),
    new Square()  // ❌ 违反里氏替换
);

for (Rectangle r : rectangles) {
    r.setWidth(10);
    r.setHeight(5);
    // 调用者期望所有 Rectangle 的行为一致
    // 但 Square 的行为不同,导致程序错误
}

为什么 Square 违反了里氏替换原则?

1. 改变了前置条件(Precondition)

java 复制代码
// Rectangle 的前置条件:setWidth 可以接受任意正数
public void setWidth(int width) {
    // 前置条件:width > 0(假设)
    this.width = width;
    // 后置条件:this.width == width,height 不变
}

// Square 的前置条件:setWidth 会同时影响 height
public void setWidth(int width) {
    this.width = width;
    this.height = width;  // ❌ 改变了后置条件
    // 后置条件:this.width == width && this.height == width
    // 与父类的后置条件不一致
}

2. 改变了后置条件(Postcondition)

java 复制代码
// Rectangle 的后置条件
// setWidth(w) 后:width == w, height 不变
// setHeight(h) 后:height == h, width 不变

// Square 的后置条件
// setWidth(w) 后:width == w, height == w ❌
// setHeight(h) 后:height == h, width == h ❌
// 与父类的后置条件不一致

3. 改变了不变式(Invariant)

java 复制代码
// Rectangle 的不变式:width 和 height 可以独立变化
// 没有强制关系:width 和 height 可以是任意值

// Square 的不变式:width == height
// 强制关系:width 必须等于 height
// 这个不变式与 Rectangle 的不变式冲突

4. 违反了行为契约(Behavioral Contract)

java 复制代码
// Rectangle 的行为契约
// 契约1:setWidth 只改变 width
// 契约2:setHeight 只改变 height
// 契约3:width 和 height 可以独立设置

// Square 违反了这些契约
// 违反契约1:setWidth 同时改变了 height
// 违反契约2:setHeight 同时改变了 width
// 违反契约3:width 和 height 不能独立设置

5. 破坏了调用者的期望

java 复制代码
// 调用者的期望(基于 Rectangle 的行为)
void processShape(Rectangle r) {
    int originalHeight = r.height;
    r.setWidth(10);
    // 期望:height 保持不变
    assert r.height == originalHeight;  // ❌ Square 会失败
    
    int originalWidth = r.width;
    r.setHeight(5);
    // 期望:width 保持不变
    assert r.width == originalWidth;  // ❌ Square 会失败
}

违反里氏替换的后果

  1. 多态失效:父类引用指向子类对象时,行为不符合预期
  2. 测试失败:基于父类行为的测试用例在子类上失败
  3. 代码错误:调用者基于父类契约编写的代码在子类上出错
  4. 维护困难:需要特殊处理子类,破坏了多态的优势

正确的设计方式

java 复制代码
// 方案1:使用组合而非继承
public interface Shape {
    int getArea();
    void resize(int width, int height);
}

public class Rectangle implements Shape {
    private int width;
    private int height;
    
    @Override
    public int getArea() {
        return width * height;
    }
    
    @Override
    public void resize(int width, int height) {
        this.width = width;
        this.height = height;
    }
}

public class Square implements Shape {
    private int side;
    
    @Override
    public int getArea() {
        return side * side;
    }
    
    @Override
    public void resize(int width, int height) {
        if (width != height) {
            throw new IllegalArgumentException("Square requires equal width and height");
        }
        this.side = width;
    }
    
    // Square 特有的方法
    public void setSide(int side) {
        this.side = side;
    }
}

// 方案2:如果必须使用继承,使用更抽象的基类
public abstract class Quadrilateral {
    public abstract int getArea();
}

public class Rectangle extends Quadrilateral {
    private int width;
    private int height;
    
    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }
    
    @Override
    public int getArea() {
        return width * height;
    }
}

public class Square extends Quadrilateral {
    private int side;
    
    public void setSide(int side) { this.side = side; }
    
    @Override
    public int getArea() {
        return side * side;
    }
}

里氏替换原则检查清单

  • 子类的前置条件不能比父类更严格
  • 子类的后置条件不能比父类更弱
  • 子类不能抛出父类未声明的异常
  • 子类不能改变父类的不变式
  • 子类的方法行为应该与父类一致

正确设计

java 复制代码
// 使用组合而非继承
public interface Shape {
    int getArea();
}

public class Rectangle implements Shape {
    private int width;
    private int height;
    // ...
}

public class Square implements Shape {
    private int side;
    // ...
}
I - 接口隔离原则(Interface Segregation Principle)

核心思想:客户端不应该依赖它不需要的接口

违反示例

java 复制代码
// 胖接口:包含太多方法
public interface Worker {
    void work();
    void eat();
    void sleep();
    void code();
    void design();
    void test();
}

// 程序员只需要部分方法
public class Programmer implements Worker {
    @Override
    public void work() { }
    
    @Override
    public void eat() { }
    
    @Override
    public void sleep() { }
    
    @Override
    public void code() { }
    
    @Override
    public void design() { }  // 不需要但必须实现
    
    @Override
    public void test() { }    // 不需要但必须实现
}

遵循接口隔离

java 复制代码
// 细粒度接口
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public interface Sleepable {
    void sleep();
}

public interface Codable {
    void code();
}

// 程序员只需实现需要的接口
public class Programmer implements Workable, Eatable, Sleepable, Codable {
    // 只实现需要的方法
}
D - 依赖倒置原则(Dependency Inversion Principle)

核心思想:高层模块不应该依赖低层模块,两者都应该依赖抽象

违反示例

java 复制代码
// 高层模块直接依赖低层模块
public class OrderService {
    private MySQLDatabase database;  // 直接依赖具体实现
    
    public void saveOrder(Order order) {
        database.save(order);
    }
}

遵循依赖倒置

java 复制代码
// 依赖抽象
public interface Database {
    void save(Object entity);
}

public class MySQLDatabase implements Database {
    @Override
    public void save(Object entity) {
        // MySQL 实现
    }
}

public class OrderService {
    private Database database;  // 依赖抽象
    
    public OrderService(Database database) {
        this.database = database;  // 依赖注入
    }
    
    public void saveOrder(Order order) {
        database.save(order);
    }
}

常见设计模式深度解析

1. 策略模式(Strategy Pattern)

定义:定义一系列算法,把它们封装起来,并且使它们可以互换

完整实现

java 复制代码
// 策略接口
public interface PaymentStrategy {
    boolean pay(double amount);
    String getPaymentMethod();
}

// 具体策略
public class CreditCardStrategy implements PaymentStrategy {
    private String cardNumber;
    private String cvv;
    
    public CreditCardStrategy(String cardNumber, String cvv) {
        this.cardNumber = cardNumber;
        this.cvv = cvv;
    }
    
    @Override
    public boolean pay(double amount) {
        System.out.println("Paying " + amount + " using credit card");
        // 调用支付网关
        return true;
    }
    
    @Override
    public String getPaymentMethod() {
        return "Credit Card";
    }
}

public class PayPalStrategy implements PaymentStrategy {
    private String email;
    
    public PayPalStrategy(String email) {
        this.email = email;
    }
    
    @Override
    public boolean pay(double amount) {
        System.out.println("Paying " + amount + " using PayPal: " + email);
        return true;
    }
    
    @Override
    public String getPaymentMethod() {
        return "PayPal";
    }
}

// 上下文类
public class PaymentContext {
    private PaymentStrategy strategy;
    
    public PaymentContext(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
    
    public boolean executePayment(double amount) {
        return strategy.pay(amount);
    }
}

// 使用示例
PaymentContext context = new PaymentContext(new CreditCardStrategy("1234", "123"));
context.executePayment(100.0);

context.setStrategy(new PayPalStrategy("user@example.com"));
context.executePayment(200.0);

Spring 中的应用

java 复制代码
// Spring 中使用策略模式
@Service
public class PaymentService {
    private final Map<String, PaymentStrategy> strategies;
    
    @Autowired
    public PaymentService(List<PaymentStrategy> strategies) {
        this.strategies = strategies.stream()
            .collect(Collectors.toMap(
                PaymentStrategy::getPaymentMethod,
                Function.identity()
            ));
    }
    
    public boolean pay(String method, double amount) {
        PaymentStrategy strategy = strategies.get(method);
        if (strategy == null) {
            throw new IllegalArgumentException("Unknown payment method: " + method);
        }
        return strategy.pay(amount);
    }
}
2. 模板方法模式(Template Method Pattern)

定义:定义一个操作中算法的骨架,而将一些步骤延迟到子类中

完整实现

java 复制代码
// 抽象模板类
public abstract class DataProcessor {
    // 模板方法:定义算法骨架
    public final void process(String data) {
        validate(data);
        String processed = transform(data);
        save(processed);
        notifyCompletion();
    }
    
    // 钩子方法:子类可以重写
    protected void validate(String data) {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Data cannot be empty");
        }
    }
    
    // 抽象方法:子类必须实现
    protected abstract String transform(String data);
    
    // 默认实现:子类可以重写
    protected void save(String data) {
        System.out.println("Saving: " + data);
    }
    
    // 钩子方法
    protected void notifyCompletion() {
        System.out.println("Processing completed");
    }
}

// 具体实现
public class XMLProcessor extends DataProcessor {
    @Override
    protected String transform(String data) {
        return "<data>" + data + "</data>";
    }
    
    @Override
    protected void save(String data) {
        System.out.println("Saving XML: " + data);
    }
}

public class JSONProcessor extends DataProcessor {
    @Override
    protected String transform(String data) {
        return "{\"data\": \"" + data + "\"}";
    }
}

Spring 中的应用

java 复制代码
// AbstractApplicationContext 中的模板方法
public abstract class AbstractApplicationContext {
    public void refresh() {
        // 模板方法
        prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        postProcessBeanFactory(beanFactory);
        invokeBeanFactoryPostProcessors(beanFactory);
        registerBeanPostProcessors(beanFactory);
        initMessageSource();
        initApplicationEventMulticaster();
        onRefresh();
        registerListeners();
        finishBeanFactoryInitialization(beanFactory);
        finishRefresh();
    }
    
    protected abstract void refreshBeanFactory();
    // 其他抽象方法...
}
3. 工厂模式(Factory Pattern)

简单工厂

java 复制代码
public class PaymentFactory {
    public static PaymentStrategy create(String type, Map<String, String> params) {
        return switch (type) {
            case "credit" -> new CreditCardStrategy(
                params.get("cardNumber"), 
                params.get("cvv")
            );
            case "paypal" -> new PayPalStrategy(params.get("email"));
            case "alipay" -> new AlipayStrategy(params.get("account"));
            default -> throw new IllegalArgumentException("Unknown payment type: " + type);
        };
    }
}

工厂方法

java 复制代码
// 抽象工厂
public abstract class PaymentFactory {
    public abstract PaymentStrategy createPaymentStrategy();
    
    public PaymentContext createPaymentContext() {
        return new PaymentContext(createPaymentStrategy());
    }
}

// 具体工厂
public class CreditCardFactory extends PaymentFactory {
    private String cardNumber;
    private String cvv;
    
    public CreditCardFactory(String cardNumber, String cvv) {
        this.cardNumber = cardNumber;
        this.cvv = cvv;
    }
    
    @Override
    public PaymentStrategy createPaymentStrategy() {
        return new CreditCardStrategy(cardNumber, cvv);
    }
}

抽象工厂

java 复制代码
// 抽象工厂接口
public interface PaymentFactory {
    PaymentStrategy createPaymentStrategy();
    PaymentValidator createPaymentValidator();
    PaymentLogger createPaymentLogger();
}

// 具体工厂
public class CreditCardPaymentFactory implements PaymentFactory {
    @Override
    public PaymentStrategy createPaymentStrategy() {
        return new CreditCardStrategy();
    }
    
    @Override
    public PaymentValidator createPaymentValidator() {
        return new CreditCardValidator();
    }
    
    @Override
    public PaymentLogger createPaymentLogger() {
        return new CreditCardLogger();
    }
}
4. 建造者模式(Builder Pattern)

完整实现

java 复制代码
public class User {
    private final String id;
    private final String name;
    private final String email;
    private final int age;
    private final List<String> roles;
    
    private User(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
        this.roles = Collections.unmodifiableList(builder.roles);
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static class Builder {
        private String id;
        private String name;
        private String email;
        private int age;
        private List<String> roles = new ArrayList<>();
        
        public Builder id(String id) {
            this.id = id;
            return this;
        }
        
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        
        public Builder email(String email) {
            this.email = email;
            return this;
        }
        
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        
        public Builder addRole(String role) {
            this.roles.add(role);
            return this;
        }
        
        public User build() {
            // 构建时验证
            if (id == null || name == null) {
                throw new IllegalStateException("id and name are required");
            }
            if (age < 0) {
                throw new IllegalArgumentException("age must be non-negative");
            }
            return new User(this);
        }
    }
}

// 使用示例
User user = User.builder()
    .id("u001")
    .name("Alice")
    .email("alice@example.com")
    .age(25)
    .addRole("USER")
    .addRole("ADMIN")
    .build();

Lombok 简化

java 复制代码
@Builder
public class User {
    @NonNull
    private String id;
    @NonNull
    private String name;
    private String email;
    @Builder.Default
    private int age = 0;
    @Singular
    private List<String> roles;
}
5. 观察者模式(Observer Pattern)

JDK 内置实现

java 复制代码
// 观察者接口
public interface Observer {
    void update(Observable o, Object arg);
}

// 被观察者
public class NewsAgency extends Observable {
    private String news;
    
    public void setNews(String news) {
        this.news = news;
        setChanged();  // 标记状态已改变
        notifyObservers(news);  // 通知所有观察者
    }
}

// 具体观察者
public class NewsChannel implements Observer {
    private String name;
    
    public NewsChannel(String name) {
        this.name = name;
    }
    
    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + " received news: " + arg);
    }
}

// 使用
NewsAgency agency = new NewsAgency();
agency.addObserver(new NewsChannel("CNN"));
agency.addObserver(new NewsChannel("BBC"));
agency.setNews("Breaking news!");

Spring 事件机制

java 复制代码
// 事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
    
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
    
    public Order getOrder() {
        return order;
    }
}

// 监听器
@Component
public class OrderEventListener {
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        Order order = event.getOrder();
        // 发送通知、更新库存等
    }
}

// 发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void createOrder(Order order) {
        // 创建订单
        // 发布事件
        eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
    }
}

源码讲解|重点拆解

示例 1:模板方法模式(Spring AbstractApplicationContext 缩影)

java 复制代码
public abstract class BaseGenerator {
    public final void generate() {
        validate();
        doGenerate();
        postProcess();
    }

    protected void validate() {}
    protected abstract void doGenerate();
    protected void postProcess() {}
}
java 复制代码
public class DocGenerator extends BaseGenerator {
    @Override
    protected void validate() {
        System.out.println("检查输入参数...");
    }
    @Override
    protected void doGenerate() {
        System.out.println("生成 Markdown 文档");
    }
}

面试亮点:解释模板方法如何固化流程、抽象类与钩子方法的关系。

示例 2:策略模式 + 依赖注入

java 复制代码
public interface PaymentStrategy {
    boolean pay(int cents);
}

public class AliPayStrategy implements PaymentStrategy {
    @Override
    public boolean pay(int cents) {
        // 调用支付宝 SDK
        return true;
    }
}

public class PaymentContext {
    private final PaymentStrategy strategy;
    public PaymentContext(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
    public boolean execute(int cents) {
        return strategy.pay(cents);
    }
}

面试亮点:展示"对扩展开放"的思路,结合 Spring Bean 注入即可在运行时自由切换策略。


实战伪代码

场景:内容发布系统(CMS)审核流程

复制代码
abstract class ContentWorkflow {
    final void process(Content content):
        preCheck(content)     // 权限校验、敏感词过滤
        enrich(content)       // 补充元数据
        publish(content)      // 持久化并发送消息
        postNotify(content)   // 通知作者/订阅者

    protected void preCheck(Content content) {}
    protected abstract void enrich(Content content)
    protected abstract void publish(Content content)
    protected void postNotify(Content content) {}
}

class VideoWorkflow extends ContentWorkflow { ... }
class ArticleWorkflow extends ContentWorkflow { ... }

技术关键词:模板方法、策略 + 工厂组合、依赖注入、事件回调。


项目实践建议

  • DDD 与分层设计:将实体、值对象、领域服务按职责拆分;Infrastructure 层提供仓储实现。
  • 契约先行:先定义接口,再实现;结合单元测试锁定行为,便于替换实现。
  • 组合优于继承:除非是"is-a"严格关系,否则优先通过组合扩展。
  • 不可变对象final 字段 + Builder;避免多线程下状态错乱。
  • 重构工具 :IDE 自动生成 getter/setter、extract interfacerename 等提升重构速度。


高频面试问答(深度解析)

1. 抽象类与接口的区别?什么时候用抽象类,什么时候用接口?

标准答案

特性 抽象类 接口
字段 可以有实例字段 只能是常量(public static final)
构造器 可以有 不能有
方法实现 可以有 默认方法(JDK 8+)
继承 单继承 多实现
访问修饰符 任意 public(JDK 9+ 支持 private)
静态方法 可以有 JDK 8+ 支持
私有方法 可以有 JDK 9+ 支持

深入追问与回答思路

Q: 为什么接口不能有构造器?

  • 接口是契约定义,不涉及对象创建
  • 实现类负责对象创建和初始化
  • 如果接口有构造器,多实现时会产生冲突

Q: 什么时候用抽象类,什么时候用接口?

  • 使用抽象类
    • 需要共享代码(模板方法模式)
    • 需要定义非静态、非 final 字段
    • 需要定义构造器
    • 需要控制访问修饰符
  • 使用接口
    • 定义契约(API 规范)
    • 需要多继承
    • 定义常量
    • 定义回调函数

Q: JDK 8 接口默认方法解决了什么问题?

  • 向后兼容:在接口中添加新方法而不破坏现有实现
  • 多继承问题:如果多个接口有相同默认方法,实现类必须重写
java 复制代码
public interface A {
    default void method() {
        System.out.println("A");
    }
}

public interface B {
    default void method() {
        System.out.println("B");
    }
}

// 编译错误:必须重写
public class C implements A, B {
    @Override
    public void method() {
        A.super.method();  // 调用 A 的默认方法
    }
}

2. 为什么倡导"组合优于继承"?

标准答案

  • 灵活性:组合可以在运行时改变行为
  • 避免层级爆炸:继承层次过深难以维护
  • 符合里氏替换:继承容易违反里氏替换原则
  • 解耦:组合降低类之间的耦合度

深入追问与回答思路

Q: 继承的问题是什么?

java 复制代码
// 问题1:层级爆炸
class Animal { }
class Dog extends Animal { }
class Poodle extends Dog { }
class ToyPoodle extends Poodle { }
// 层级过深,难以维护

// 问题2:违反里氏替换
class Rectangle {
    void setWidth(int w) { }
    void setHeight(int h) { }
}
class Square extends Rectangle {
    void setWidth(int w) {
        super.setWidth(w);
        super.setHeight(w);  // 改变了父类行为
    }
}

// 问题3:紧耦合
class MyList extends ArrayList {
    // 依赖 ArrayList 的实现细节
    // 如果 ArrayList 改变,可能影响 MyList
}

Q: 组合的优势是什么?

java 复制代码
// 使用组合
public class MyList<T> {
    private final List<T> list;  // 组合而非继承
    
    public MyList() {
        this.list = new ArrayList<>();
    }
    
    // 只暴露需要的方法
    public void add(T item) {
        list.add(item);
    }
    
    // 可以轻松切换实现
    public void switchToLinkedList() {
        // 不能直接切换,但设计时可以考虑
    }
}

// 更灵活的设计
public class MyList<T> {
    private List<T> list;
    
    public MyList(List<T> list) {
        this.list = list;  // 依赖注入
    }
}

Q: 什么时候应该用继承?

  • 真正的 is-a 关系:Dog is a Animal
  • 需要多态:需要父类引用指向子类对象
  • 需要共享代码:多个子类有共同实现
  • 框架扩展点:框架设计时提供扩展点

3. 重写 equalshashCode 的注意事项?

标准答案

  • equals 的四个性质:自反性、对称性、传递性、一致性
  • hashCode 契约:相等的对象必须有相同的 hashCode
  • 重写 equals 必须重写 hashCode:否则违反 hashCode 契约
  • 使用工具类Objects.equals()Objects.hash()

深入追问与回答思路

Q: equals 的四个性质是什么?

java 复制代码
// 1. 自反性:x.equals(x) 必须为 true
assert obj.equals(obj);

// 2. 对称性:x.equals(y) == y.equals(x)
assert a.equals(b) == b.equals(a);

// 3. 传递性:x.equals(y) && y.equals(z) => x.equals(z)
assert a.equals(b) && b.equals(c) ? a.equals(c) : true;

// 4. 一致性:多次调用结果相同
assert obj.equals(other) == obj.equals(other);

Q: 为什么重写 equals 必须重写 hashCode?

java 复制代码
// 违反契约的示例
public class Person {
    private String name;
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Person) {
            return name.equals(((Person) obj).name);
        }
        return false;
    }
    
    // 没有重写 hashCode
}

// 问题:相等的对象可能有不同的 hashCode
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
p1.equals(p2);  // true
p1.hashCode() != p2.hashCode();  // 可能为 true,违反契约

// 在 HashMap 中会导致问题
Map<Person, String> map = new HashMap<>();
map.put(p1, "value1");
map.get(p2);  // 返回 null,因为 hashCode 不同

Q: 正确的实现方式?

java 复制代码
public class Person {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;  // 性能优化
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);  // 使用工具类
    }
}

Q: 为什么使用 Objects.equals()?

java 复制代码
// 错误方式:可能 NPE
public boolean equals(Object obj) {
    Person other = (Person) obj;
    return name.equals(other.name);  // name 为 null 时 NPE
}

// 正确方式:处理 null
public boolean equals(Object obj) {
    if (obj == null) return false;
    Person other = (Person) obj;
    return Objects.equals(name, other.name);  // 自动处理 null
}

4. 如何理解依赖倒置原则?

标准答案

  • 高层模块不依赖低层模块:两者都依赖抽象
  • 抽象不应该依赖细节:细节应该依赖抽象
  • 实现方式:接口 + 依赖注入

深入追问与回答思路

Q: 依赖倒置解决了什么问题?

java 复制代码
// 违反依赖倒置:高层模块依赖低层模块
public class OrderService {
    private MySQLDatabase database;  // 直接依赖具体实现
    
    public void saveOrder(Order order) {
        database.save(order);
    }
}

// 问题:如果要切换数据库,需要修改 OrderService

Q: 如何实现依赖倒置?

java 复制代码
// 1. 定义抽象
public interface Database {
    void save(Object entity);
}

// 2. 低层模块实现抽象
public class MySQLDatabase implements Database {
    @Override
    public void save(Object entity) {
        // MySQL 实现
    }
}

public class PostgreSQLDatabase implements Database {
    @Override
    public void save(Object entity) {
        // PostgreSQL 实现
    }
}

// 3. 高层模块依赖抽象
public class OrderService {
    private Database database;  // 依赖抽象
    
    public OrderService(Database database) {
        this.database = database;  // 依赖注入
    }
    
    public void saveOrder(Order order) {
        database.save(order);
    }
}

// 4. 使用
OrderService service = new OrderService(new MySQLDatabase());
// 可以轻松切换
OrderService service2 = new OrderService(new PostgreSQLDatabase());

Q: Spring 如何实现依赖倒置?

java 复制代码
// 使用接口
public interface UserRepository {
    User findById(Long id);
}

// 实现
@Repository
public class JpaUserRepository implements UserRepository {
    // JPA 实现
}

// 高层模块依赖接口
@Service
public class UserService {
    private final UserRepository repository;  // 依赖接口
    
    @Autowired
    public UserService(UserRepository repository) {
        this.repository = repository;  // Spring 自动注入
    }
}

5. finalfinallyfinalize 有何差别?

标准答案

  • final:修饰符,修饰类、方法、变量
  • finally:异常处理块,保证执行
  • finalize:Object 的方法,已过时(JDK 18 移除)

深入追问与回答思路

Q: final 的三种用法?

java 复制代码
// 1. final 类:不能被继承
public final class String {
    // ...
}

// 2. final 方法:不能被重写
public class Parent {
    public final void method() {
        // 子类不能重写
    }
}

// 3. final 变量:只能赋值一次
public void method() {
    final int x = 10;  // 基本类型:值不能改变
    final List<String> list = new ArrayList<>();  // 引用类型:引用不能改变
    list.add("item");  // 但可以修改对象内容
    // list = new ArrayList<>();  // 编译错误
}

Q: finally 块什么时候不执行?

java 复制代码
// 1. System.exit() 时
try {
    System.exit(0);
} finally {
    System.out.println("不会执行");
}

// 2. JVM 崩溃时
try {
    // 导致 JVM 崩溃的代码
} finally {
    System.out.println("不会执行");
}

// 3. 守护线程被终止时

Q: 为什么 finalize 被移除?

  • 不可靠:不保证执行时机
  • 性能问题:影响 GC 性能
  • 替代方案 :使用 try-with-resourcesCleaner API
java 复制代码
// JDK 9+ 推荐使用 Cleaner
public class Resource implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();
    private final Cleaner.Cleanable cleanable;
    
    public Resource() {
        cleanable = cleaner.register(this, new ResourceCleaner());
    }
    
    @Override
    public void close() {
        cleanable.clean();
    }
    
    private static class ResourceCleaner implements Runnable {
        @Override
        public void run() {
            // 清理资源
        }
    }
}

6. 面向对象 vs 面向过程?

标准答案

  • 面向对象:以对象为中心,强调封装、继承、多态
  • 面向过程:以过程为中心,强调函数和步骤
  • 适用场景:OOP 适合复杂系统,过程式适合简单脚本

深入追问与回答思路

Q: 两者的核心区别?

java 复制代码
// 面向过程:数据和操作分离
int[] numbers = {1, 2, 3, 4, 5};
int sum = calculateSum(numbers);
int max = findMax(numbers);

// 面向对象:数据和操作封装在一起
class NumberList {
    private int[] numbers;
    
    public NumberList(int[] numbers) {
        this.numbers = numbers;
    }
    
    public int sum() {
        // 计算逻辑
    }
    
    public int max() {
        // 查找逻辑
    }
}

Q: 什么时候用面向过程?

  • 简单脚本和工具
  • 性能敏感的场景(如算法实现)
  • 函数式编程风格

Q: Java 中如何结合两者?

  • 工具类使用静态方法(面向过程风格)
  • 业务逻辑使用对象(面向对象风格)
  • 使用函数式接口(Lambda)实现函数式风格

面试代码练习建议

  • 实现一个简化版依赖注入容器(读取配置 → 反射实例化 → 注入依赖)。
  • 设计开闭可扩展的通知系统(Email/SMS/Push),并使用策略模式切换。
  • 将遗留继承体系重构为组合 + 委托,记录重构步骤与收益。

延伸阅读

  • 《Head First 设计模式》《Design Patterns: Elements of Reusable Object-Oriented Software》
  • Martin Fowler:《Refactoring》《Patterns of Enterprise Application Architecture》
  • 《Clean Architecture》:聚焦架构分层与依赖倒置
  • GitHub Awesome 系列:awesome-design-patternsjava-design-patterns
相关推荐
vortex52 小时前
ORM是什么?如何理解ORM?ORM的优缺点?
java·数据库·sql·mysql·oracle·orm
Algebraaaaa2 小时前
为什么线程阻塞要用.join而不是.wait
java·c++·python
巴拉巴拉~~2 小时前
Flutter 通用滑块组件 CommonSliderWidget:单值 / 范围 + 刻度 + 标签 + 样式自定义
开发语言·前端·javascript
是苏浙2 小时前
零基础入门Java之设计图书管理系统
java·开发语言
墨雪不会编程2 小时前
C++内存管理深度剖析
java·开发语言·c++
BBB努力学习程序设计2 小时前
Java Scanner完全指南:让程序与用户对话
java
BBB努力学习程序设计2 小时前
Java面向对象编程:封装、继承与多态深度解析
java
Lucky_Turtle2 小时前
【Springboot】解决PageHelper在实体转Vo下出现total数据问题
java·spring boot·后端
Mr.朱鹏2 小时前
大模型入门学习路径(Java开发者版)下
java·python·学习·微服务·langchain·大模型·llm