典型的設計缺陷會阻止類不可變
使用一些 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 屬性標記為 private
和 final
。
在 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
的。