行为型模式(上)——策略、模板方法与观察者
摘要
行为型设计模式(Behavioral Patterns)关注对象之间的通信与职责分配——不是对象长什么样,而是对象如何协作、如何传递消息、如何在运行时灵活地改变行为。本文深入剖析三个最核心的行为型模式。策略模式(Strategy) 解决”算法族的封装与互换”问题:当同一个操作有多种实现方式时,将每种方式封装为独立的策略对象,让调用方在运行时选择——这是消灭大量 if-else / switch 的标准手段,也是 OCP 最直接的落地;深入分析策略模式在 Java 8 函数式接口中的演进,以及它与工厂方法如何配合实现优雅的策略注册机制。模板方法模式(Template Method) 解决”算法骨架固定、步骤可定制”的问题:父类定义不变的执行流程,子类只实现变化的步骤——这是 OCP 在继承体系中的体现,Spring JdbcTemplate、AbstractQueuedSynchronizer 都是工业级应用;剖析钩子方法(Hook Method)的设计哲学与反例。观察者模式(Observer) 解决”对象间一对多的依赖通知”问题:当一个对象状态变化时,所有依赖它的对象都应自动收到通知——这是发布-订阅模式的基础,是事件驱动架构的核心;分析 Java 内置的 Observer/Observable、Guava EventBus、Spring ApplicationEvent 机制的实现差异与选择依据。
第 1 章 策略模式(Strategy)
1.1 动机:算法变化引发的 if-else 泥潭
策略模式(Strategy Pattern)的动机来自一个极为普遍的代码坏味道:当同一个功能有多种实现方式,且需要在运行时选择时,最直觉的做法是写一大堆 if-else 或 switch:
// 反例:促销折扣计算,随着业务发展不断膨胀的 if-else
public class DiscountCalculator {
public double calculate(Order order, String promotionType) {
double price = order.getOriginalPrice();
if ("NORMAL".equals(promotionType)) {
// 普通用户:无折扣
return price;
} else if ("VIP".equals(promotionType)) {
// VIP 用户:九折
return price * 0.9;
} else if ("SUPER_VIP".equals(promotionType)) {
// 超级 VIP:八折
return price * 0.8;
} else if ("COUPON".equals(promotionType)) {
// 优惠券:减固定金额
return Math.max(0, price - order.getCouponAmount());
} else if ("DOUBLE_ELEVEN".equals(promotionType)) {
// 双十一满减:满 200 减 30
return price - (long)(price / 200) * 30;
} else if ("BIRTHDAY".equals(promotionType)) {
// 生日特惠:七五折
return price * 0.75;
} else if ("FLASH_SALE".equals(promotionType)) {
// 限时秒杀:五折
return price * 0.5;
}
// 未来还会加更多...
throw new IllegalArgumentException("Unknown promotion: " + promotionType);
}
}这段代码的问题已经在 OCP 章节分析过——每次新增促销类型都需要修改这个方法,且随着促销规则越来越复杂,整个方法会变得难以理解和测试。
策略模式的核心思想是:将每种算法(促销规则)封装到独立的类中,让这些类实现同一个接口,调用方持有接口引用,在运行时注入不同的实现。
1.2 策略模式的结构
// 策略接口:所有促销规则的公共契约
@FunctionalInterface // 只有一个方法,可以作为函数式接口
public interface DiscountStrategy {
double apply(Order order);
}
// 具体策略一:VIP 折扣
public class VipDiscountStrategy implements DiscountStrategy {
private final double discountRate;
public VipDiscountStrategy(double discountRate) {
this.discountRate = discountRate;
}
@Override
public double apply(Order order) {
return order.getOriginalPrice() * discountRate;
}
}
// 具体策略二:优惠券减免
public class CouponDiscountStrategy implements DiscountStrategy {
@Override
public double apply(Order order) {
return Math.max(0, order.getOriginalPrice() - order.getCouponAmount());
}
}
// 具体策略三:满减(双十一)
public class FullReductionStrategy implements DiscountStrategy {
private final double threshold; // 满减门槛(如 200)
private final double reduction; // 减免金额(如 30)
public FullReductionStrategy(double threshold, double reduction) {
this.threshold = threshold;
this.reduction = reduction;
}
@Override
public double apply(Order order) {
double price = order.getOriginalPrice();
return price - (long)(price / threshold) * reduction;
}
}
// 上下文(Context):持有策略引用,委托给策略执行
public class DiscountCalculator {
private DiscountStrategy strategy;
public DiscountCalculator(DiscountStrategy strategy) {
this.strategy = strategy;
}
// 允许运行时切换策略
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double calculate(Order order) {
return strategy.apply(order); // 委托给策略,自己不包含任何 if-else
}
}1.3 策略的注册与查找:消灭调用方的 if-else
策略模式解决了策略内部的 if-else,但调用方仍然需要知道”什么条件用什么策略”。如果这个选择逻辑还是用 if-else 来做,问题只是从一处转移到了另一处。
优雅的解法:策略注册表(Strategy Registry),用 Map<String, DiscountStrategy> 将条件直接映射到策略对象,彻底消灭 if-else:
// 策略注册表:将策略类型与策略实现绑定
@Component
public class DiscountStrategyRegistry {
private final Map<String, DiscountStrategy> strategies = new HashMap<>();
// Spring 会自动注入所有 DiscountStrategy 实现类(通过 @Qualifier 或 Bean 名称)
// 更简单的做法:在构造器中手动注册
public DiscountStrategyRegistry() {
// 注册所有策略
strategies.put("VIP", new VipDiscountStrategy(0.9));
strategies.put("SUPER_VIP", new VipDiscountStrategy(0.8));
strategies.put("BIRTHDAY", new VipDiscountStrategy(0.75));
strategies.put("FLASH_SALE", new VipDiscountStrategy(0.5));
strategies.put("COUPON", new CouponDiscountStrategy());
strategies.put("DOUBLE_ELEVEN", new FullReductionStrategy(200, 30));
strategies.put("NORMAL", order -> order.getOriginalPrice()); // Lambda!
}
public DiscountStrategy getStrategy(String promotionType) {
DiscountStrategy strategy = strategies.get(promotionType);
if (strategy == null) {
throw new IllegalArgumentException("Unknown promotion type: " + promotionType);
}
return strategy;
}
}
// 调用方:完全没有 if-else
@Service
public class OrderService {
@Autowired private DiscountStrategyRegistry registry;
public double calculateFinalPrice(Order order) {
DiscountStrategy strategy = registry.getStrategy(order.getPromotionType());
return strategy.apply(order);
}
}现在,新增一种促销类型只需要:① 创建新的 DiscountStrategy 实现(或一个 Lambda);② 在 DiscountStrategyRegistry 中 put 一行注册。调用方代码(OrderService)零改动,严格遵守 OCP。
1.4 Java 8 之后:Lambda 就是策略
Java 8 引入函数式接口后,策略模式的实现变得极为轻量。当策略接口只有一个方法(@FunctionalInterface)时,策略对象可以直接用 Lambda 表达式或方法引用表示,无需创建专门的实现类:
// 在注册表中,简单的策略用 Lambda 表达式内联定义
strategies.put("NORMAL", order -> order.getOriginalPrice());
strategies.put("VIP", order -> order.getOriginalPrice() * 0.9);
strategies.put("BIRTHDAY", order -> order.getOriginalPrice() * 0.75);
// 复杂策略仍然可以是独立类(便于测试和复用)
strategies.put("DOUBLE_ELEVEN", new FullReductionStrategy(200, 30));Comparator、Runnable、Predicate、Function 这些 JDK 内置函数式接口,本质上都是策略接口——它们都封装了一种”可变的算法或行为”,允许调用方在运行时注入不同的实现。Collections.sort(list, comparator) 就是经典的策略模式应用:comparator 就是”比较策略”。
1.5 策略模式 vs 简单工厂:职责不同
策略模式和工厂方法都与”选择不同实现”有关,容易混淆,但两者职责截然不同:
- 工厂方法关注”如何创建对象”——客户端不知道具体创建哪个类,由工厂决定;
- 策略模式关注”如何执行算法”——客户端知道自己需要什么策略,但策略内部是黑箱。
在实践中,它们经常组合使用:注册表(Map)扮演工厂角色,负责策略对象的查找和创建;策略接口定义算法行为,供调用方使用。
第 2 章 模板方法模式(Template Method)
2.1 动机:流程固定、步骤可变
模板方法模式(Template Method Pattern)解决的问题是:一个算法的整体执行流程是固定的,但其中某些步骤的具体实现因场景而异。
最典型的生活类比是”制作饮料”的过程:
- 烧水(固定步骤,不变)
- 把原料放入杯中(变化:茶叶 vs 咖啡粉)
- 用热水冲泡(固定步骤,不变)
- 根据喜好加调料(变化:茶加柠檬 vs 咖啡加糖和牛奶)
如果不用模板方法,“制作茶”和”制作咖啡”的代码会包含大量重复:
// 反例:重复的骨架代码
public class TeaMaker {
public void makeTea() {
boilWater(); // 重复
steepTeaLeaves(); // 茶特有
pourInCup(); // 重复
addLemon(); // 可选
}
}
public class CoffeeMaker {
public void makeCoffee() {
boilWater(); // 重复(与上面一模一样)
brewCoffeeGrinds(); // 咖啡特有
pourInCup(); // 重复(与上面一模一样)
addSugarAndMilk(); // 可选
}
}如果”冲泡流程”需要加一步(如”先温杯”),需要修改所有具体类,这正是代码重复带来的维护问题。
2.2 模板方法的结构
模板方法将不变的流程骨架定义在父类中,将变化的步骤定义为 protected abstract 方法(或提供默认实现的 protected 方法),由子类覆写:
// 抽象父类:定义算法骨架
public abstract class BeverageMaker {
// 模板方法(final:禁止子类覆写,保护骨架不被破坏)
public final void prepareBeverage() {
boilWater(); // 固定步骤:烧水
brew(); // 抽象步骤:冲泡(子类实现)
pourInCup(); // 固定步骤:倒入杯中
if (customerWantsCondiments()) { // 钩子方法:是否加调料
addCondiments(); // 抽象步骤:加调料(子类实现)
}
}
// 固定步骤(父类提供实现,子类不需要也不能覆写)
private void boilWater() {
System.out.println("Boiling water...");
}
private void pourInCup() {
System.out.println("Pouring into cup...");
}
// 抽象步骤:强制子类提供实现
protected abstract void brew();
protected abstract void addCondiments();
// 钩子方法(Hook):提供默认行为,子类可选择覆写
// 默认加调料,子类可覆写返回 false 表示不加
protected boolean customerWantsCondiments() {
return true;
}
}
// 具体子类一:泡茶
public class TeaMaker extends BeverageMaker {
@Override
protected void brew() {
System.out.println("Steeping the tea leaves for 3 minutes...");
}
@Override
protected void addCondiments() {
System.out.println("Adding lemon...");
}
// 不覆写 customerWantsCondiments(),默认加调料
}
// 具体子类二:冲咖啡(不喝加料咖啡的纯粹主义者版本)
public class BlackCoffeeMaker extends BeverageMaker {
@Override
protected void brew() {
System.out.println("Dripping coffee through filter...");
}
@Override
protected void addCondiments() {
// 空实现,但实际上 customerWantsCondiments() 返回 false 就不会调用
}
@Override
protected boolean customerWantsCondiments() {
return false; // 黑咖啡不加任何东西
}
}2.3 钩子方法(Hook Method)的设计哲学
钩子方法是模板方法模式中一个微妙而重要的设计点,值得单独深入分析。
什么是钩子方法:钩子方法是父类中提供了默认实现(通常是空实现或返回布尔值)的 protected 方法,子类可以(但不必须)覆写它来改变模板方法的行为。
与抽象方法(abstract,子类必须实现)不同,钩子方法是可选的扩展点。
钩子方法的两种形式:
// 形式一:返回布尔值的条件钩子(控制流程分支)
protected boolean customerWantsCondiments() { return true; }
// 形式二:空实现的事件钩子(在流程的关键节点插入额外逻辑)
protected void afterBrew() { } // 默认不做任何事,子类可覆写做额外处理为什么需要钩子方法:如果把所有步骤都定义为 abstract,每个子类都必须实现所有步骤,但某些子类可能对某些步骤没有任何有意义的实现——它们会被迫写空方法或 throw new UnsupportedOperationException(),这是 ISP 的违反。钩子方法通过提供默认(通常是空)实现,让子类只覆写真正有差异的部分。
2.4 工业级应用:Spring JdbcTemplate
Spring 的 JdbcTemplate 是模板方法模式最著名的工业级应用。其核心思路是:JDBC 操作的固定步骤(获取连接、创建语句、处理异常、关闭资源)由 JdbcTemplate 统一处理,变化的步骤(SQL 语句、参数绑定、结果集处理)由调用方以回调形式提供。
// JdbcTemplate 的核心模板方法(简化版)
public <T> T execute(StatementCallback<T> action) {
Connection con = DataSourceUtils.getConnection(getDataSource()); // 固定:获取连接
try {
Statement stmt = con.createStatement(); // 固定:创建 Statement
applyStatementSettings(stmt); // 固定:设置超时等参数
T result = action.doInStatement(stmt); // 变化:调用方提供的具体 SQL 逻辑
handleWarnings(stmt); // 固定:处理警告
return result;
} catch (SQLException ex) {
throw translateException("StatementCallback", getSql(action), ex); // 固定:异常转换
} finally {
JdbcUtils.closeStatement(stmt); // 固定:关闭资源
DataSourceUtils.releaseConnection(con, getDataSource()); // 固定:释放连接
}
}调用方只需提供”变化的部分”——SQL 逻辑:
// 调用方只关注 SQL,不需要关心连接管理和异常处理
List<User> users = jdbcTemplate.query(
"SELECT * FROM users WHERE status = ?",
new Object[]{1},
(rs, rowNum) -> { // 这个 Lambda 就是"结果集处理"的策略/钩子
return User.builder()
.id(rs.getLong("id"))
.name(rs.getString("name"))
.build();
}
);JdbcTemplate 同时体现了模板方法模式(固定的 JDBC 流程骨架)和策略模式(可替换的 SQL 逻辑和结果映射逻辑)。
2.5 模板方法的边界与反例
模板方法 vs 策略模式的选择:
| 维度 | 模板方法(继承) | 策略模式(组合) |
|---|---|---|
| 扩展机制 | 继承 + 覆写 | 实现接口 + 注入 |
| 骨架控制 | 父类控制(final),子类无法改变 | 调用方自由组合策略 |
| 耦合度 | 子类与父类强耦合 | 策略与上下文松耦合 |
| 单元测试 | 需要通过子类测试 | 策略可独立单元测试 |
| 运行时切换 | 不支持(继承在编译期固定) | 支持(运行时注入不同策略) |
| 适用场景 | 变体少、骨架稳定、框架扩展点 | 变体多、频繁切换、需独立测试 |
现代 Java 开发(尤其是 Spring 生态)更倾向于策略模式(组合优于继承),模板方法主要在框架层(JdbcTemplate、AbstractQueuedSynchronizer、AbstractList)中发挥作用。
第 3 章 观察者模式(Observer)
3.1 动机:对象间的耦合通知问题
观察者模式(Observer Pattern)解决的是一个非常普遍的问题:当一个对象(被观察者/主题)的状态发生变化时,依赖它的多个对象(观察者)需要自动得到通知并做出响应。
最直觉但最错误的做法是:在被观察者的状态变化代码里,直接调用每个关心方的方法:
// 反例:订单状态变化时,直接调用所有依赖方
public class Order {
private OrderStatus status;
@Autowired private EmailService emailService;
@Autowired private InventoryService inventoryService;
@Autowired private PointsService pointsService;
@Autowired private LogisticsService logisticsService;
@Autowired private StatisticsService statisticsService;
public void complete() {
this.status = OrderStatus.COMPLETED;
// Order 直接调用所有关心"订单完成"事件的模块
emailService.sendOrderCompletionEmail(this);
inventoryService.confirmDeduction(this);
pointsService.addCompletionPoints(this);
logisticsService.notifyShipment(this);
statisticsService.recordCompletedOrder(this);
// 未来还会加更多...
}
}这段代码的 Order 类违反了 SRP:它不仅负责订单业务逻辑,还承担了对多个外部系统的通知职责。更严重的是:
- 高耦合:
Order直接依赖 5 个 Service,任何一个 Service 变化(如EmailService接口改变)都可能影响Order; - 扩展困难:新增一个”订单完成后推送到 BI 系统”的需求,必须修改
Order类; - 无法解耦发布与订阅时序:所有通知都是同步的,如果某个通知耗时(如发送邮件),会拖慢订单完成的响应速度。
3.2 观察者模式的结构
观察者模式将”通知谁”的决策权从被观察者中剥离出去,由外部注册机制决定:
// 观察者接口(Observer/Listener)
public interface OrderEventListener {
void onOrderCompleted(Order order);
}
// 被观察者(Subject/Publisher):维护观察者列表,状态变化时遍历通知
public class Order {
private OrderStatus status;
private final List<OrderEventListener> listeners = new ArrayList<>();
// 注册观察者
public void addListener(OrderEventListener listener) {
listeners.add(listener);
}
// 移除观察者
public void removeListener(OrderEventListener listener) {
listeners.remove(listener);
}
// 业务方法:状态变化后通知所有观察者
public void complete() {
this.status = OrderStatus.COMPLETED;
// Order 不再知道具体有哪些观察者,只负责通知
notifyListeners();
}
private void notifyListeners() {
for (OrderEventListener listener : new ArrayList<>(listeners)) {
listener.onOrderCompleted(this);
}
}
}
// 具体观察者一:邮件通知
@Component
public class EmailOrderListener implements OrderEventListener {
@Override
public void onOrderCompleted(Order order) {
emailService.sendOrderCompletionEmail(order.getUserId(), order.getId());
}
}
// 具体观察者二:积分服务
@Component
public class PointsOrderListener implements OrderEventListener {
@Override
public void onOrderCompleted(Order order) {
pointsService.addPoints(order.getUserId(), order.getAmount());
}
}现在,新增”订单完成后推送到 BI 系统”只需新增一个 BiSystemOrderListener,注册到 Order 中——Order 类本身不需要任何修改。
3.3 从手工实现到事件驱动框架
上面的手工实现虽然体现了观察者模式,但有局限:
- 每个”主题”(
Order、User等)都要维护自己的观察者列表,代码重复; - 所有通知都是同步调用,无法做到异步;
- 观察者与主题之间仍然有直接依赖(需要显式注册)。
现代框架提供了更完善的事件机制。
Guava EventBus:
Guava 的 EventBus 是一个进程内的发布-订阅总线,通过注解 @Subscribe 声明事件处理方法,彻底解耦发布者和订阅者:
// 事件类(普通 POJO)
public class OrderCompletedEvent {
private final Long orderId;
private final Long userId;
private final BigDecimal amount;
// 构造器 + getter ...
}
// 订阅者:通过 @Subscribe 声明感兴趣的事件类型
public class EmailNotificationSubscriber {
@Subscribe // Guava EventBus 注解
public void onOrderCompleted(OrderCompletedEvent event) {
emailService.send(event.getUserId(), "Order " + event.getOrderId() + " completed");
}
}
// 发布者:只依赖 EventBus,不知道有哪些订阅者
@Service
public class OrderService {
private final EventBus eventBus;
public void completeOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
order.complete();
orderRepository.save(order);
// 发布事件,不关心谁来处理
eventBus.post(new OrderCompletedEvent(order.getId(), order.getUserId(), order.getAmount()));
}
}
// 应用启动时注册订阅者
eventBus.register(new EmailNotificationSubscriber());
eventBus.register(new PointsSubscriber());Spring ApplicationEvent:
Spring 框架内置了完善的事件机制,基于 ApplicationEvent + ApplicationListener 接口(或 @EventListener 注解):
// 自定义事件(继承 ApplicationEvent)
public class OrderCompletedEvent extends ApplicationEvent {
private final Order order;
public OrderCompletedEvent(Object source, Order order) {
super(source);
this.order = order;
}
public Order getOrder() { return order; }
}
// 发布事件:通过 ApplicationEventPublisher 发布
@Service
public class OrderService {
@Autowired private ApplicationEventPublisher eventPublisher;
@Transactional
public void completeOrder(Long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
order.complete();
orderRepository.save(order);
// 发布 Spring 事件(在事务提交后)
eventPublisher.publishEvent(new OrderCompletedEvent(this, order));
}
}
// 监听事件(@EventListener 注解)
@Component
public class EmailNotificationListener {
@EventListener
@Async // 加上 @Async 变成异步处理,不阻塞主流程
public void handleOrderCompleted(OrderCompletedEvent event) {
Order order = event.getOrder();
emailService.sendOrderCompletionEmail(order.getUserId());
}
}
// 或者用 @TransactionalEventListener:确保在事务提交后才处理事件
@Component
public class InventoryListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderCompleted(OrderCompletedEvent event) {
// 只有订单事务成功提交后,才真正确认库存扣减
inventoryService.confirmDeduction(event.getOrder());
}
}@TransactionalEventListener 是 Spring 观察者机制的一个重要特性:它保证只有在发布事件的事务成功提交后,事件处理才会被触发。这解决了一个常见的分布式一致性问题——如果订单入库失败(事务回滚),不应该发送”订单完成”邮件。
3.4 观察者模式的三个层次对比
| 实现方式 | 耦合程度 | 异步支持 | 适用场景 |
|---|---|---|---|
| 手工实现(维护 List) | 观察者与主题轻耦合 | 不支持(需手动) | 简单场景、教学 |
| Guava EventBus | 完全解耦(通过注解) | AsyncEventBus 支持 | 进程内事件,无 Spring 依赖 |
| Spring ApplicationEvent | 完全解耦 | @Async 支持,@TransactionalEventListener 事务感知 | Spring 项目首选 |
| 消息队列(Kafka/RabbitMQ) | 完全解耦(跨进程) | 天然异步 | 微服务间事件,跨系统通知 |
观察者模式的内存泄漏风险
手工实现的观察者模式有一个常见的内存泄漏陷阱:如果观察者被注册后没有被移除(
removeListener()),而观察者本身持有大量资源(或是一个短生命周期的对象),那么这个观察者对象会被被观察者的listeners列表持有,永远无法被 GC 回收。使用 Spring 的事件机制可以避免这个问题,因为 Spring 容器管理监听器 Bean 的生命周期。
总结
本篇深入剖析了三个最核心的行为型模式:
-
策略模式将可变的算法封装为独立的策略类,让调用方在运行时注入。消灭
if-else的关键不只是定义策略接口,还需要配合”策略注册表”(Map<String, Strategy>)来消灭策略选择本身的if-else。Java 8 之后,函数式接口让简单策略可以直接用 Lambda 表示,无需创建专门的类。策略模式与工厂方法经常组合使用——注册表负责策略的创建和查找,策略接口负责算法执行; -
模板方法模式通过继承将不变的算法骨架固化在父类,变化的步骤下放到子类。钩子方法(
protected非abstract方法)提供可选的扩展点,让子类只需覆写有差异的部分。SpringJdbcTemplate是最权威的工业级实践。在现代设计中,策略模式(组合)比模板方法(继承)更受推荐,但在框架扩展点设计(如 Spring 的各种Abstract*类)中,模板方法仍是最优选择; -
观察者模式通过注册机制将发布者与订阅者解耦,让状态变化的通知可以任意扩展而不影响被观察者。手工实现适合简单场景;Spring
ApplicationEvent+@EventListener是 Spring 项目的首选,配合@Async实现异步通知,配合@TransactionalEventListener保证事务一致性——这是生产环境中处理”订单完成后做很多事”的标准模式。
下一篇继续行为型模式中间部分:责任链(Netty ChannelPipeline 的核心)、命令模式(操作对象化,支持撤销/重做)、迭代器(统一遍历接口的本质):07 行为型模式(中)——责任链、命令与迭代器。
参考资料
- GoF,《Design Patterns: Elements of Reusable Object-Oriented Software》, 1994
- Spring Framework 文档:Application Events and Listeners
- Guava 文档:EventBus
- Martin Fowler,《Refactoring》, Replace Conditional with Polymorphism(策略模式的重构出发点)
思考题
- 组合模式将对象组织为树形结构。文件系统(目录包含文件和子目录)是经典案例。在组合模式中,叶节点和组合节点实现相同的接口——但叶节点不支持
addChild()等操作。对叶节点调用addChild()应该抛异常还是静默忽略?‘透明性’和’安全性’两种设计取舍在实际项目中如何选择?- 享元模式(Flyweight)共享细粒度对象以节省内存。Java 的
Integer.valueOf()在 -128 到 127 范围内缓存 Integer 对象——这就是享元模式。Integer a = 127; Integer b = 127; a == b返回true,但Integer a = 128; Integer b = 128; a == b返回false。这个行为在什么场景下会导致微妙的 bug?- String 常量池也是享元模式的应用。
String.intern()将字符串放入常量池并返回池中的引用。在 JDK 7 之前,常量池在永久代(PermGen),大量intern()可能导致 PermGen OOM。JDK 7+ 常量池移到了堆中——但intern()仍然有性能开销。在什么场景下使用intern()是值得的?