什么是不可变对象

不可变对象(Immutable Object)指的是对象一旦创建之后,其状态就不能被改变。String 类就是一个典型的不可变类,它的对象一旦创建,值就无法被修改。

不可变对象的优势

不可变对象在开发中具有显著优势,主要体现在以下两点:

  1. 缓存友好:不可变对象非常适合缓存,因为你不需要担心它的值会被意外更改。
  2. 线程安全:不可变类自身是线程安全的。在多线程环境下,不需要额外的同步措施即可安全共享,从而避免了复杂的线程安全问题。

创建不可变类的步骤

要创建一个真正的不可变类,建议遵循以下七个步骤:

  1. 声明为 final:将类声明为 final,防止被继承,避免子类破坏不可变性。
  2. 私有化成员变量:将所有成员变量声明为 private,禁止外部直接访问。
  3. 不提供 setter 方法:确保对象创建后,外部无法修改其成员变量。
  4. 成员变量声明为 final:将所有成员变量声明为 final,确保它们只能被赋值一次。
  5. 构造器深拷贝:通过构造器初始化所有成员。如果成员是可变对象(如集合),必须进行深拷贝(Deep Copy)。
  6. Getter 方法返回拷贝:在 getter 方法中,不要直接返回可变对象的引用,而是返回其克隆或拷贝。
  7. 示例说明:为了深入理解第 5 和第 6 条关于深拷贝与浅拷贝的区别,下文将通过 FinalClassExample 类进行详细阐明。

代码示例

以下代码展示了如何实现一个不可变类。重点在于构造器中的深拷贝处理,以及 getter 方法中对可变成员的保护。

import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

    private final int id;

    private final String name;

    private final HashMap testMap;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    /**
     * 可变对象的访问方法
     * 返回拷贝而不是原始引用
     */
    public HashMap getTestMap() {
        // return testMap; // 浅拷贝:直接返回引用(不安全)
        return (HashMap) testMap.clone(); // 深拷贝:返回克隆对象(安全)
    }

    /**
     * 实现深拷贝 (deep copy) 的构造器
     * @param i id
     * @param n name
     * @param hm testMap
     */
    public FinalClassExample(int i, String n, HashMap hm) {
        System.out.println("Performing Deep Copy for Object initialization");
        this.id = i;
        this.name = n;
        HashMap tempMap = new HashMap();
        String key;
        Iterator it = hm.keySet().iterator();
        while (it.hasNext()) {
            key = (String) it.next();
            tempMap.put(key, hm.get(key));
        }
        this.testMap = tempMap;
    }

    /**
     * 实现浅拷贝 (shallow copy) 的构造器
     * 注意:为了演示不可变性的破坏,此处代码被注释
     * @param i
     * @param n
     * @param hm
     */
    /*
    public FinalClassExample(int i, String n, HashMap hm) {
        System.out.println("Performing Shallow Copy for Object initialization");
        this.id = i;
        this.name = n;
        this.testMap = hm;
    }
    */

    /**
     * 测试浅拷贝的结果
     * 为了创建不可变类,要使用深拷贝
     * @param args
     */
    public static void main(String[] args) {
        HashMap h1 = new HashMap();
        h1.put("1", "first");
        h1.put("2", "second");

        String s = "original";

        int i = 10;

        FinalClassExample ce = new FinalClassExample(i, s, h1);

        // 检查是值拷贝还是引用拷贝
        System.out.println(s == ce.getName());
        System.out.println(h1 == ce.getTestMap());

        // 打印初始值
        System.out.println("ce id:" + ce.getId());
        System.out.println("ce name:" + ce.getName());
        System.out.println("ce testMap:" + ce.getTestMap());

        // 修改局部变量的值
        i = 20;
        s = "modified";
        h1.put("3", "third");

        // 再次打印值
        System.out.println("ce id after local variable change:" + ce.getId());
        System.out.println("ce name after local variable change:" + ce.getName());
        System.out.println("ce testMap after local variable change:" + ce.getTestMap());

        // 尝试通过 accessor 方法修改内部变量
        HashMap hmTest = ce.getTestMap();
        hmTest.put("4", "new");

        System.out.println("ce testMap after changing variable from accessor methods:" + ce.getTestMap());
    }
}

运行结果与分析

1. 深拷贝模式(默认)

使用上述代码(构造器执行深拷贝,getter 返回克隆对象)运行,结果如下:

Performing Deep Copy for Object initialization
true
false
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{2=second, 1=first}
ce testMap after changing variable from accessor methods:{2=second, 1=first}

分析

  • h1 == ce.getTestMap() 输出 false,说明内部地图是独立的拷贝。
  • 即使外部修改了 h1 或通过 getter 获取到的地图副本,对象内部的状态 ce testMap 始终保持不变。这证明了不可变性的成功实现。

2. 浅拷贝模式(对比测试)

现在,我们注释掉深拷贝的构造器,取消对浅拷贝构造器的注释。同时,对 getTestMap() 方法中的返回语句取消注释,使其直接返回实际的对象引用。再次执行代码,结果如下:

Performing Shallow Copy for Object initialization
true
true
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{3=third, 2=second, 1=first}
ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=first, 4=new}

分析

  • h1 == ce.getTestMap() 输出 true,说明内部地图与外部传入的是同一个引用。
  • 当外部修改 h1 或通过 getter 获取引用后修改时,ce testMap 的值随之改变。
  • 从输出可以看出,HashMap 的值被更改了,因为构造器实现的是浅拷贝,而且在 getter 方法中返回的是原本对象的引用。这破坏了类的不可变性。

总结

编写不可变类的核心在于切断外部修改内部状态的所有路径。对于基本类型和不可变对象(如 String),直接赋值即可;对于可变对象(如集合、数组),必须在构造器中进行深拷贝,并在 getter 方法中返回拷贝而非引用。

说明

  1. 本文代码示例为了突出核心逻辑,使用了 Java 泛型引入前的原始类型(Raw Types,如 HashMap 而非 HashMap<String, String>)。在实际现代 Java 开发中,建议配合泛型使用以确保类型安全。
  2. 对于集合类型的深拷贝,现代开发中可借助 Collections.unmodifiableMap 或第三方库(如 Guava)来简化不可变集合的创建。
  3. 代码逻辑适用于 Java 主流版本,核心原则在任何面向对象语言中均通用。