深度探索:Java 中不可变类的真相

158 阅读2分钟

在 Java 中,不可变类 (Immutable Class) 是指一旦创建后,其状态就不能被改变的类。不可变类中的对象在创建后,其所有字段都无法被修改,从而保证了对象的不变性。不可变性对于多线程环境特别重要,因为它可以避免并发访问时的数据竞争和同步问题。

不可变类的特点

  1. 字段不可变

    • 所有字段都应该是 final 类型,这意味着一旦对象创建完成,这些字段就不能被更改。
    • 如果字段是对象类型,那么这些对象也应该是不可变的。
  2. 构造函数

    • 构造函数应该是私有的,以防止外部直接创建对象。
    • 构造函数应该使用 final 参数,以确保传递给构造函数的参数在构造过程中不会被改变。
  3. 防御性复制

    • 如果对象包含可变的成员(如集合或数组),那么应该在构造函数中进行防御性复制,以确保外部对象不会影响到不可变对象的状态。
    • 例如,可以使用 Collections.unmodifiableList() 或 Arrays.copyOf() 来创建不可变的集合或数组副本。
  4. 不可变的引用类型

    • 如果字段是引用类型,那么这个引用类型也应该不可变。
    • 例如,如果字段是 String 类型,那么 String 本身就是不可变的。
  5. 没有公共的 setter 方法

    • 不可变类不应该提供任何可以改变其状态的方法,即不应该有公共的 setter 方法。
  6. 线程安全性

    • 由于不可变对象的状态在创建后就不会改变,因此它们天生就是线程安全的,可以在多个线程间共享而不需要额外的同步措施。
  7. 哈希码和相等性

    • 不可变类应该重写 hashCode() 和 equals(Object obj) 方法,以确保对象相等性的一致性。
    • 通常,这些方法应该基于类中的所有字段来计算。

示例代码

下面是一个简单的不可变类的示例:

public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final List<String> hobbies;
    
    public ImmutablePerson(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    public List<String> getHobbies() {
        return hobbies;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ImmutablePerson that = (ImmutablePerson) o;
        return age == that.age &&
               Objects.equals(name, that.name) &&
               Objects.equals(hobbies, that.hobbies);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age, hobbies);
    }
}

总结

不可变类提供了一种简单的方式来确保对象的状态不会被意外地改变。它们对于多线程环境特别有用,因为它们可以避免同步问题,并且可以安全地在多个线程之间共享。通过遵循上述设计原则,可以轻松地创建出安全可靠的不可变类。