作者 :小股虫
标签 :Java、设计模式、代码重构、Effective Java、性能优化
适用人群:Java 中高级开发者、准备面试者、追求代码质量的工程师
一、学习目标
- 掌握《Effective Java》中关于对象创建与静态工厂方法的核心原则
- 理解并熟练应用关键设计模式:单例模式(5种实现)、工厂方法、策略模式
- 运用代码重构技巧提升项目可维护性与性能
- 完成三项实践任务:优化单例实现、重构重复逻辑、撰写技术博客
二、核心知识点详解
1. 《Effective Java》核心原则
✅ 避免创建不必要的对象
问题:频繁创建相同功能的对象会增加 GC 压力,降低性能。
示例:
java
// 不推荐:每次调用都创建新对象
String s = new String("hello");
// 推荐:使用字符串字面量(JVM 自动复用)
String s = "hello";
最佳实践:
- 对不可变对象(如 String、Integer)尽量复用;
- 使用缓存(如 ConcurrentHashMap 缓存计算结果);
- 谨慎使用对象池(除非对象创建成本极高)。
✅ 使用静态工厂方法替代构造器
优势:
- 方法有名字,语义更清晰(如
BigInteger.probablePrime()); - 可返回子类型对象(如
Collections.unmodifiableList()); - 可控制实例数量(如单例、享元);
- 支持泛型类型推断(避免冗长泛型声明)。
示例:
java
public class BooleanUtils {
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE; // 复用常量
}
}
注意事项:
- 静态工厂方法不能被继承;
- 需在文档中明确说明是否返回缓存实例、是否线程安全;
- 不要让用户误以为"轻量"而滥用(内部可能很重)。
📌 参考 :《Effective Java》Item 1 & Item 5
🔗 延伸建议:Item 6(避免 finalize)、Item 17(最小化可变性)、Item 42(优先使用 Lambda)
2. 单例模式(Singleton Pattern)
单例模式确保一个类只有一个实例,并提供全局访问点。根据初始化时机与线程安全性,有多种实现方式。
(1) 饿汉式(Eager Initialization)
特点 :类加载时即创建实例,线程安全,但无延迟加载。
适用场景:实例占用资源小,且一定会被使用。
java
public class SingletonEager {
// 类加载时即初始化
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() {}
public static SingletonEager getInstance() {
return INSTANCE;
}
}
⚠️ 安全性:线程安全 ✅,但不防反射攻击 ❌,若实现 Serializable 则需 readResolve() 防序列化攻击。
(2) 懒汉式(Lazy Initialization,非线程安全)
特点 :首次调用时创建实例,节省资源,但多线程下不安全。
仅适用于单线程环境(不推荐生产使用)。
java
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy(); // 多线程下可能创建多个实例
}
return instance;
}
}
⚠️ 安全性:线程安全 ❌,防反射 ❌,防序列化 ❌。
(3) 双重校验锁(Double-Checked Locking)
特点 :延迟加载 + 线程安全 + 性能较高。
关键:volatile 防止指令重排序。
java
public class SingletonDCL {
private static volatile SingletonDCL instance;
private SingletonDCL() {}
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
单元测试验证线程安全:
java
@Test
public void testSingletonThreadSafety() throws InterruptedException {
Set<SingletonDCL> instances = ConcurrentHashMap.newKeySet();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> instances.add(SingletonDCL.getInstance()));
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
assertEquals(1, instances.size());
}
⚠️ 安全性:线程安全 ✅,但不防反射攻击 ❌,若实现 Serializable 则需 readResolve() 防序列化攻击。
(4) 静态内部类(Holder 模式)
特点 :利用 JVM 类加载机制实现延迟加载 + 线程安全,无锁、简洁、高效。
推荐作为默认实现。
java
public class SingletonHolder {
private SingletonHolder() {}
private static class Holder {
static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static SingletonHolder getInstance() {
return Holder.INSTANCE;
}
}
✅ 安全性:
- 线程安全 ✅(JVM 保证);
- 若未实现 Serializable,则天然不会被序列化,默认防序列化攻击;
- 但仍不防反射攻击(可通过构造器绕过);
- 一旦实现 Serializable,必须添加 readResolve()。
(5) 枚举单例(Enum Singleton)
特点 :最简洁、最安全的单例实现。
天然防止反射和序列化攻击。
java
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
// 业务逻辑
}
}
✅ 安全性:
- 线程安全 ✅;
- 延迟加载 ✅(枚举常量在首次使用时初始化);
- 防反射攻击 ✅(Java 禁止通过反射创建枚举实例);
- 防序列化攻击 ✅(反序列化时通过 name 查找已有实例,不会新建)。
💡 Joshua Bloch 在《Effective Java》Item 3 中强烈推荐此方式。
🔒 单例安全特性对比总结
| 实现方式 | 线程安全 | 延迟加载 | 防反射攻击 | 防序列化攻击 | 推荐度 |
|---|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ❌ | ❌(需 readResolve) | ★★☆ |
| 懒汉式(非线程安全) | ❌ | ✅ | ❌ | ❌ | ✘ |
| 双重校验锁 | ✅ | ✅ | ❌ | ❌(需 readResolve) | ★★★★ |
| 静态内部类(Holder) | ✅ | ✅ | ❌ | ✅ | ★★★★★ |
| 枚举单例 | ✅ | ✅ | ✅ | ✅ | ★★★★☆ |
注:静态内部类"默认防序列化攻击"的前提是未实现 Serializable。一旦实现,必须手动添加 readResolve() 方法。
▶ 如何防御序列化攻击?
若单例类实现了 Serializable,必须添加:
java
private Object readResolve() {
return getInstance(); // 或直接返回 Holder.INSTANCE / INSTANCE(枚举无需)
}
▶ 如何防御反射攻击?
在构造器中检查实例是否已存在(对非枚举有效):
java
private SingletonDCL() {
if (instance != null) {
throw new RuntimeException("Singleton already initialized!");
}
}
⚠️ 此方法对静态内部类无效(因 instance 是内部类字段),唯一完全防反射的是枚举。
3. 工厂方法模式(Factory Method)
目的:将对象创建逻辑封装,解耦客户端与具体类。
结构:
- 抽象产品(Product)
- 具体产品(ConcreteProductA/B)
- 工厂(Creator)
java
interface PaymentProcessor {
void process(double amount);
}
class AlipayProcessor implements PaymentProcessor {
public void process(double amount) { /* 支付宝逻辑 */ }
}
class WechatProcessor implements PaymentProcessor {
public void process(double amount) { /* 微信逻辑 */ }
}
class PaymentFactory {
public static PaymentProcessor create(String type) {
return switch (type.toLowerCase()) {
case "alipay" -> new AlipayProcessor();
case "wechat" -> new WechatProcessor();
default -> throw new IllegalArgumentException("Unknown payment type");
};
}
}
4. 策略模式 + 工厂模式组合(高阶实践)
典型场景:动态选择算法(如折扣、税率、支付渠道)。
组合优势:策略定义行为,工厂管理创建,两者结合实现"开闭原则"。
java
@FunctionalInterface
interface TaxStrategy {
BigDecimal calculate(BigDecimal income);
}
class ChinaTax implements TaxStrategy {
public BigDecimal calculate(BigDecimal income) {
return income.multiply(BigDecimal.valueOf(0.1));
}
}
class USTax implements TaxStrategy {
public BigDecimal calculate(BigDecimal income) {
return income.multiply(BigDecimal.valueOf(0.15));
}
}
// 策略工厂
class TaxStrategyFactory {
private static final Map<String, TaxStrategy> STRATEGIES = Map.of(
"CN", new ChinaTax(),
"US", new USTax()
);
public static TaxStrategy get(String country) {
return STRATEGIES.get(country);
}
}
🌟 进阶 :在 Spring 中可通过 Map<String, TaxStrategy> 自动注入所有策略 Bean,实现插件化扩展。
5. 策略模式与函数式编程
Java 8+ 后,策略可用 Lambda 表达式简化:
java
// 无需定义类
TaxStrategy cnTax = income -> income.multiply(BigDecimal.valueOf(0.1));
// 或直接内联
orderService.setDiscountStrategy(price -> price * 0.75);
函数式带来的额外价值:
- 组合性:
Predicate.and(),Function.andThen() - 惰性求值:Stream 中间操作不立即执行
- 无状态:纯函数更易测试和并行
6. 代码重构技巧
(1) 消除重复代码(DRY 原则)
- 提取公共方法;
- 使用模板方法或策略模式抽象差异点;
- 利用工具类封装通用逻辑。
(2) 优化循环
- 避免在循环内重复计算;
- 减少嵌套层级(提前 return 或 continue);
- 考虑使用 Stream 替代传统 for 循环。
java
// 传统写法
for (var item : list) {
if (item != null && item.isActive()) {
// ...
}
}
// 函数式写法
list.stream()
.filter(Objects::nonNull)
.filter(Item::isActive)
.forEach(this::processItem);
(3) 使用函数式编程提升可读性与扩展性
- 用
Function<T, R>、Predicate<T>、Consumer<T>替代匿名类; - 链式调用表达业务流程;
- 支持并行流(
.parallelStream())轻松实现多线程处理。
三、总结
- 单例模式 :
- 日常开发推荐 静态内部类;
- 安全敏感系统(如金融、认证)推荐 枚举单例;
- 避免使用非线程安全的懒汉式。
- 工厂 + 策略:是消除条件分支、提升扩展性的黄金组合;
- 静态工厂方法:不仅是语法糖,更是设计灵活性的体现;
- 重构必须有测试护航:行为不变是底线,性能提升是加分项。
代码不是写出来的,是重构出来的。
每一次小优化,都是向"优雅"靠近的一步。
四、参考文献
- Joshua Bloch. 《Effective Java》(第3版)
- Martin Fowler. 《重构:改善既有代码的设计》
- GoF. 《设计模式:可复用面向对象软件的基础》
- Oracle Java Tutorials: Lambda Expressions, Concurrency, Serialization
欢迎点赞、收藏、评论交流!
关注我,持续更新 Java 高质量编码系列。