典型的設計缺陷會阻止類不可變

使用一些 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 的。