典型的设计缺陷会阻止类不可变

使用一些 setter,而不在构造函数中设置所有需要的属性

public final class Person { // example of a bad immutability
    private final String name;
    private final String surname;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
    public String getSurname() { return surname;}
    public void setSurname(String surname) { this.surname = surname); }
}

很容易证明 Person 类不是不可变的:

Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation

要修复它,只需删除 setSurname() 并重构构造函数,如下所示:

public Person(String name, String surname) {
    this.name = name;
    this.surname = surname;
  }

不将实例变量标记为私有和最终

看看下面的类:

public final class Person {
    public String name;
    public Person(String name) {
        this.name = name;
     }
    public String getName() {
        return name;
    }
    
}

以下代码段显示上述类不是不可变的:

Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation

要修复它,只需将 name 属性标记为 privatefinal

在 getter 中公开类的可变对象

看看下面的类:

import java.util.List;
import java.util.ArrayList;
public final class Names {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }
    public List<String> getNames() {
        return names;
    }
    public int size() {
        return names.size();
    }
}

Names 类在第一眼看上去似乎是不可变的,但它不像下面的代码所示:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

发生这种情况是因为 getNames() 返回的引用列表的更改可以修改 Names 的实际列表。

为了解决这个问题,只要避免返回引用类的可变对象的引用或者通过使防守副本,具体如下:

public List<String> getNames() {
   return new ArrayList<String>(this.names); // copies elements
}

或者通过设计 getter,只返回其他不可变对象基元,如下所示:

public String getName(int index) {
    return names.get(index);
}
public int size() {
    return names.size();
}

使用可在不可变类之外修改的对象注入构造函数

这是前一个缺陷的变种。看看下面的类:

import java.util.List;
public final class NewNames {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = names;
    }
    public String getName(int index) {
        return names.get(index);
    }
    public int size() {
        return names.size();
    }
}

就像之前的 Names 类一样,NewNames 类在第一眼就看似不变,但事实并非如此,事实上以下片段证明了相反:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

要解决这个问题,就像上一个缺陷一样,只需创建对象的防御性副本,而不将其直接分配给不可变类,即构造函数可以更改如下:

    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }

让类的方法被覆盖

看看下面的类:

public class Person {
    private final String name;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
}

Person 类在第一眼看上去似乎是不可变的,但假设定义了一个新的 Person 子类:

public class MutablePerson extends Person {
    private String newName;
    public MutablePerson(String name) {
        super(name);            
    }
    @Override
    public String getName() {
        return newName;
    }
    public void setName(String name) {
        newName = name;
    }
}

现在 Person(im)可变性可以通过使用新的子类通过多态来利用:

Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike    

为了解决这个问题,无论是类标记为 final 所以它不能扩展声明的所有构造函数(S)为 private 的。