典型的设计缺陷会阻止类不可变
使用一些 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
的。