6.4.7 不可变类

6.4.7 不可变类immutable

什么是不可变类

不可变类的意思是创建该类的实例后,该实例的实例变量是不可改变的

Java中的不可变类

Java提供的8个包装类java.lang.String类都是不可变类,当创建它们的实例后,其实例的实例变量不可改变。

如何自定义不可变类

  1. 使用privatefinal修饰符来修饰该类的成员变量
  2. 提供带参数构造器,用于根据传入参数来初始化类里的成员变量。
  3. 仅为该类的成员变量提供getter方法,不要为该类的成员变量提供setter方法,因为普通方法无法修改final修饰的成员变量
  4. 如果有必要,重写Object类的hashCode()equals()方法.
    • equals()方法根据关键成员变量来作为两个对象是否相等的标准,
    • 还应该保证两个用equals方法判断为相等的对象的hashCode也相等。

什么是可变类

可变类的含义是该类的实例变量是可变的。大部分时候所创建的类都是可变类,特别是JavaBean,因为总是为其实例变量提供了settergetter方法。

成员变量类型是可变类的情况

当使用final修饰引用类型变量时,仅表示这个引用类型变量不可被重新赋值,但引用类型变量所指向的对象的成员变量值依然可改变。这就产生了一个问题:当创建不可变类时,如果它包含成员变量的类型是可变的,那么其对象的成员变量的值依然是可改变的,则这个类一人不是不可变类。

如果需要设计一个不可变类,尤其要注意其引用类型的成员变量,如果引用类型的成员变量的类是可变的,则要想办法让外界无法正确获取成员变量的引用.

当成员变量的类型是可变的的时候如何设计不可变类

  1. 在构造器中不要将可变类的参数直接赋值给成员变量,而是取出参数的值,然后再封装成新对象,再将这个新对象赋值给成员变量.
  2. getter方法中也不要直接返回成员变量对象,而是取出成员变量对象中的值,再封装成新的一个对象返回.

这样外界无法获取成员变量的引用,从而无法修改成员变量对象的值.从而保证这个类是真正的不可变类.

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 这是一个可变类
class Name {
private String firstName;
private String lastName;

public Name() {
}

public Name(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

// 省略firstName、lastName的setter和getter方法
public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getFirstName() {
return this.firstName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getLastName() {
return this.lastName;
}
}

public class Person {
// final修饰的成员变量不可以赋值新对象,但是对象里面的值可以修改
private final Name name;

public Person(Name name) {
// 成员变量和name引用的不是同一个对象
// 外部的name引用变量无法修改成员变量对象的值
this.name = new Name(name.getFirstName(), name.getLastName());
}

/**
* 不直接返回成员变量,而是返回可变成员变量的一个副本,避免被外界修改
*
* @return
*/
public Name getName() {
// 返回的是新的对象,外界的引用依然无法修改成员变量的值
return new Name(name.getFirstName(), name.getLastName());
}

public static void main(String[] args) {
Name n = new Name("悟空", "孙");
Person p = new Person(n);
// 获取的永远是副本
System.out.println(p.getName().getFirstName());
// 无法修改到p对象中的成员变量
n.setFirstName("八戒");
// 获取的永远是副本
System.out.println(p.getName().getFirstName());
}
}

运行结果:

1
2
悟空
悟空