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 的三种用法:
-
final 类:不能被继承
javapublic final class ImmutablePoint { // 不能被继承 } // 编译错误 // public class MutablePoint extends ImmutablePoint { } -
final 方法:不能被重写
javapublic class Parent { public final void method() { // 子类不能重写 } } public class Child extends Parent { // 编译错误:不能重写 final 方法 // @Override // public void method() { } } -
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 会失败
}
违反里氏替换的后果:
- 多态失效:父类引用指向子类对象时,行为不符合预期
- 测试失败:基于父类行为的测试用例在子类上失败
- 代码错误:调用者基于父类契约编写的代码在子类上出错
- 维护困难:需要特殊处理子类,破坏了多态的优势
正确的设计方式:
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 interface、rename等提升重构速度。
高频面试问答(深度解析)
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. 重写 equals 和 hashCode 的注意事项?
标准答案:
- 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. final、finally、finalize 有何差别?
标准答案:
- 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-resources或CleanerAPI
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-patterns、java-design-patterns