每日Java面试系列(15):进阶篇(String不可变的原因、性能问题、String三剑客、自定义不可变设计、组合优于继承等相关问题)
今日一句:若你现在肯努力,最坏的结果也只是大器晚成。
系列介绍
"Java面试基础篇"系列!本系列旨在帮助Java开发者系统性地准备面试,每天精选至少5道经典面试题,涵盖Java基础、进阶、框架等各方面知识。坚持学习21天,助你面试通关!
基础面试题:
每日5题Java面试系列基础(1)
每日5题Java面试系列基础(2)
每日5题Java面试系列基础(3)
每日5题Java面试系列基础(4)
每日5题Java面试系列基础(5)
每日5题Java面试系列基础(6)
进阶面试题:
每日5题Java面试系列进阶(7)
每日5题Java面试系列进阶(8)
每日5题Java面试系列进阶(9)
每日5题Java面试系列进阶(10)
每日5题Java面试系列进阶(11)
每日5题Java面试系列进阶(12)
每日5题Java面试系列进阶(13)
每日5题Java面试系列进阶(14)
一、不可变类深度剖析
1. 典型不可变类及设计原因
类名 | 设计目的 | 不可变性的价值 |
---|---|---|
Integer | 封装基本类型,支持对象操作 | 线程安全(原子性)、缓存优化(-128~127) |
BigDecimal | 精确金融计算 | 防止计算中途篡改导致结果错误 |
LocalDateTime | 日期时间处理 | 时间值天然应不可变,避免并发修改 |
Collections.unmodifiableList() | 创建只读集合 | 防御性编程,防止外部修改集合内容 |
设计本质:
通过 final class
+ private final字段
+ 无setter
+ 返回副本而非引用 实现,如 BigDecimal.add()
:
public BigDecimal add(BigDecimal augend) {// 创建新对象而非修改自身return new BigDecimal(this.intVal + augend.intVal, this.scale);
}
2. String不可变性的性能问题与优化
- 问题根源:
String s = ""; for (int i = 0; i < 10000; i++) {s += i; // 每次循环产生新String对象,旧对象等待GC }
- 产生 10000个中间对象,内存与GC压力巨大
- 优化方案:
- StringBuilder(非线程安全):
StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) {sb.append(i); } String result = sb.toString(); // 仅最终生成一个String
- JDK9+ 紧凑字符串(Compact Strings):
- 底层将
char[]
改为byte[]
+ 编码标记,内存占用降低40%
- 底层将
- 字符串常量池复用:字面量相同则复用对象
- StringBuilder(非线程安全):
3. StringBuilder vs StringBuffer vs String
特性 | String | StringBuilder | StringBuffer |
---|---|---|---|
可变性 | ❌ 不可变 | ⭕ 可变 | ⭕ 可变 |
线程安全 | ⭐ 天然安全 | ❌ 不安全 | ⭐ synchronized 方法 |
性能 | ❌ 频繁拼接性能差 | ⭐⭐ 单线程首选 | ⭐ 多线程安全但性能较低 |
内存占用 | 高(对象数多) | 低(单对象操作) | 低(单对象操作) |
4. 自定义不可变类设计规范
public final class ImmutablePoint {// 1. final类 + final字段private final int x;private final int y;private final List<String> labels; // 引用类型需特殊处理// 2. 构造器初始化所有状态public ImmutablePoint(int x, int y, List<String> labels) {this.x = x;this.y = y;// 3. 防御性复制:防止外部修改传入的集合this.labels = Collections.unmodifiableList(new ArrayList<>(labels));}// 4. 不提供setter,只提供getterpublic int getX() { return x; }public List<String> getLabels() {// 5. 返回不可变副本return Collections.unmodifiableList(labels);}
}
关键防御点:对引用类型字段做深度保护(深拷贝或返回只读视图)
二、组合优于继承实战解析
1. 重构案例:支付渠道系统
- 继承方案痛点:
PaymentChannel
AlipayChannel
+validate()
WechatChannel
+validate()
BankCardChannel
+validate()
- 新增渠道需继承基类,重写方法易遗漏
- 渠道验证逻辑分散,无法复用通用规则
- 组合重构方案:
public interface Validator {boolean validate(PaymentRequest request); }public class PaymentChannel {private final Validator validator; // 组合验证策略private final String channelName;public PaymentChannel(Validator validator, String name) {this.validator = validator;this.channelName = name;}public boolean process(PaymentRequest request) {if (validator.validate(request)) {// 执行支付return true;}return false;} }// 使用示例:组合不同验证策略 Validator strictValidator = new StrictValidator(); PaymentChannel alipay = new PaymentChannel(strictValidator, "Alipay");
2. 组合 vs 聚合关键区别
关系 | 组合(Composition) | 聚合(Aggregation) |
---|---|---|
生命周期 | 整体控制部分的生命周期(同生共死) | 整体与部分独立存在 |
强度 | ⭐⭐⭐ 强关系 | ⭐⭐ 中关系 |
代码表现 | 部分在整体构造函数中创建 | 部分通过参数传入整体 |
UML | 实心菱形箭头 | 空心菱形箭头 |
案例 | Car 拥有 Engine (车毁引擎亡) | Department 包含 Employee (部门解散员工仍在) |
3. 接口+组合实现灵活设计
设计模板:
public class OrderService {private final PaymentProcessor processor; // 接口组合private final LoggingService logger; // 接口组合public OrderService(PaymentProcessor processor, LoggingService logger) {this.processor = processor;this.logger = logger;}public void placeOrder(Order order) {processor.charge(order.getAmount());logger.log("Order placed: " + order.getId());}
}// 可插拔实现
interface PaymentProcessor { void charge(BigDecimal amount); }
interface LoggingService { void log(String message); }// 使用时注入不同实现
OrderService service = new OrderService(new AlipayProcessor(), new CloudLogger());
4. 体现"组合优于继承"的设计模式
模式 | 组合体现 | 解决继承痛点 |
---|---|---|
策略模式 | 将算法封装为独立策略对象注入Context | 避免算法实现污染主体类 |
装饰器模式 | 通过嵌套装饰器对象动态扩展功能 | 替代多层继承导致的类爆炸 |
桥接模式 | 抽象部分与实现部分通过组合连接 | 防止多维变化产生类数量乘积增长 |
代理模式 | 代理对象组合真实对象,控制访问行为 | 无需继承即可增强功能 |
回答技巧点睛
- 源码佐证:展示
Integer.valueOf()
的缓存机制源码片段 - 性能数据:对比
String
拼接与StringBuilder
在10万次操作的耗时(如 5000ms vs 5ms) - 反例警示:指出
java.util.Properties
继承Hashtable
导致可插入非String键值的设计缺陷 - 架构演进:描述从继承重构为组合后,支付渠道系统扩展效率提升(新渠道开发从1天→1小时)
- 设计原则:引用《Effective Java》第18条:“组合优先于继承”