在 Java 中,可变类型(Mutable)和 不可变类型(Immutable)的设计与内存管理、性能优化密切相关。下面通过 字符串(String) 的核心机制和示例代码,详细解释它们的区别、原理及实际应用。
一、不可变类型(Immutable)的核心特性
不可变对象一旦创建,其状态(内容)不可被修改。任何“修改”操作都会生成新对象。
1. 不可变类的设计原则
- 所有字段声明为
final
和private
:防止外部修改。 - 不提供修改状态的
setter
方法:只能通过构造函数初始化。 - 防御性拷贝(Defensive Copy):若类包含可变对象(如
Date
),返回它们的深拷贝副本。 - 类声明为
final
:防止子类重写方法破坏不可变性。
2. Java 中常见的不可变类型
String
:最经典的不可变类。- 基本类型的包装类:如
Integer
,Double
,Boolean
。 - 日期时间类:如
LocalDateTime
(Java 8+)。 BigInteger
和BigDecimal
:高精度计算类。
二、可变类型(Mutable)的特性
可变对象允许直接修改内部状态,无需创建新对象。例如:
StringBuilder
/StringBuffer
:可变的字符序列。- 集合类:
ArrayList
,HashMap
等。 - 自定义类:包含
setter
方法的类。
三、字符串(String)的不可变性与内存机制
1. 字符串的两种创建方式
// 方式1:字面量赋值 → 触发常量池机制
String s1 = "hello";
String s2 = "hello";
// 方式2:new 关键字 → 强制在堆中创建新对象
String s3 = new String("hello");
String s4 = new String("hello");
2. 字符串比较的底层原理
==
比较对象地址:System.out.println(s1 == s2); // true(常量池复用) System.out.println(s3 == s4); // false(堆中不同对象)
equals()
比较内容:System.out.println(s1.equals(s3)); // true(内容相同)
3. 字符串常量池(String Pool)
- 位置:方法区(Java 7 前在永久代,Java 7+ 移至堆内存)。
- 机制:
- 使用字面量(如
"hello"
)创建字符串时,JVM 检查常量池是否存在该字符串。 - 若存在,直接返回池中对象的引用;若不存在,创建新对象并放入池中。
- 使用字面量(如
new String()
绕过常量池:- 无论常量池是否存在该字符串,
new
都会在堆中创建新对象。
- 无论常量池是否存在该字符串,
4. intern()
方法:强制使用常量池
String s5 = new String("hello").intern();
String s6 = "hello";
System.out.println(s5 == s6); // true(s5 被放入常量池)
四、不可变类型的设计优势
1. 线程安全
- 不可变对象天然线程安全,无需同步(如
String
在多线程中无需加锁)。
2. 缓存优化
- 字符串常量池:减少重复字符串的内存开销。
- 包装类缓存:如
Integer
缓存 -128~127 的数值。
3. 哈希键的稳定性
- 不可变对象的哈希值不会改变,适合作为
HashMap
的键。
五、可变类型的适用场景
1. 需要频繁修改内容
StringBuilder
:单线程下高效修改字符串。StringBuffer
:线程安全但性能较低(方法用synchronized
修饰)。
2. 动态数据结构
- 集合类:如
ArrayList.add()
,HashMap.put()
直接修改内部数据。
六、字符串内存模型图解
String s1 = "hello"; // 常量池中创建对象
String s2 = "hello"; // 复用常量池对象
String s3 = new String("hello"); // 堆中新对象
String s4 = s3.intern(); // 返回常量池引用
七、不可变类的实现示例
自定义不可变类 ImmutablePerson
public final class ImmutablePerson {
private final String name;
private final int age;
private final Date birthDate; // Date 是可变对象!
public ImmutablePerson(String name, int age, Date birthDate) {
this.name = name;
this.age = age;
// 防御性拷贝:防止外部修改影响内部状态
this.birthDate = new Date(birthDate.getTime());
}
public Date getBirthDate() {
// 返回拷贝,避免内部数据被修改
return new Date(birthDate.getTime());
}
}
八、总结:可变与不可变的核心对比
特性 | 不可变类型(如 String ) | 可变类型(如 StringBuilder ) |
---|---|---|
内容修改 | 创建新对象,原对象不变 | 直接修改原对象 |
线程安全 | 天然安全 | 需手动同步(如 StringBuffer ) |
内存优化 | 常量池、缓存机制 | 无特殊优化 |
适用场景 | 多线程、哈希键、配置类 | 单线程高频修改、动态集合操作 |
掌握可变与不可变类型的区别,能帮助你写出更高效、健壮的代码。例如:
- 优先使用不可变对象:除非有明确的修改需求。
- 避免共享可变对象:防止多线程竞争或意外修改。