为什么需要不可变类?

不可变类一旦创建,其状态就不能被修改,这带来了多个方面的好处

包括:

  • 线程安全性
  • 简化代码
  • 提高性能

不可变类不代表类内变量不可变,而是状态(字段值)不可变


线程安全性

多线程环境中的安全性:由于不可变对象一旦创建就不能改变状态,它们天生是线程安全的。

多个线程可以安全地共享和访问同一个不可变对象,而不需要同步机制来避免并发修改的问题。


简化代码

减少复杂性:不可变对象的状态不会改变,因此在处理它们时,不需要考虑对象状态的变化。这简化了代码,减少了错误的可能性。

安全性:不可变对象可以安全地传递给方法、存储在集合中或返回给调用者,而不需要担心它们会在其他地方被修改。


提高性能

缓存:不可变对象可以被缓存和重用。一个典型的例子是字符串池(String Pool),其中相同的字符串字面量被缓存,以减少内存使用和提高性能。

哈希码缓存:由于不可变对象的状态不变,它们的哈希码也不会变化。因此,可以在创建对象时计算并缓存哈希码,以提高集合(如哈希表和哈希集)的性能。


易于维护和调试

更容易理解和维护:由于不可变对象的状态一旦创建就不变,理解和维护这些对象的代码变得更加容易。开发者可以确信,一旦对象被构建,它的状态不会在后续代码中被改变。


避免副作用

无副作用:不可变对象在方法调用中不会产生副作用。方法调用返回结果,不会改变对象本身的状态,因此不需要担心对象状态在方法调用后被意外改变。


不可变类示例

一个典型的不可变类的设计示例是 Java 中的 String 类。

String 类内部使用 private final char[] 存储字符串,通过私有+不暴露setter方法来维持不可变


创建不可变类的规则

要创建一个不可变类,需要遵循以下规则:

  1. 声明类为 final:防止子类继承并改变类的行为。
  2. 所有字段都声明为 privatefinal:防止字段在对象创建后被修改。
  3. 没有提供修改字段的方法(setter 方法):确保对象的状态在创建后不能被改变。
  4. 确保对象的状态在构造时完全初始化:通过构造方法初始化所有字段。
  5. 如果类包含可变对象的字段,确保这些字段在对象创建后不能被修改:
    • 在构造方法中,使用防御性复制来初始化这些字段。
    • 不要直接暴露这些可变对象的引用。

示例:包含可变对象的不可变类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.Date;

public final class ImmutableEvent {
private final String name;
private final Date date;

public ImmutableEvent(String name, Date date) {
this.name = name;
// 使用防御性复制,确保外部不能修改 date 的值
this.date = new Date(date.getTime());
}

public String getName() {
return name;
}

public Date getDate() {
// 返回 date 的防御性复制
return new Date(date.getTime());
}
}

在这个示例中,通过使用防御性复制来初始化 Date 对象,并在 getter 方法中返回 Date 对象的防御性复制,确保了不可变性。


结论

不可变类提供了线程安全性、简化代码、提高性能和避免副作用等诸多优势。

通过遵循创建不可变类的规则,开发者可以设计出更加健壮和易于维护的代码。

不可变类在多线程编程、集合类的使用以及确保数据一致性等方面具有重要意义。