1. 什么是不可变对象
不可变对象也称之为值对象,《Effective Java》一书中给出这样的定义:不可变对象是指每个对象中包含的所有信息都必须在创建该对象时提供,并在对象的整个生命周期内固定不变。比如下面这段代码:
public class ImmutableObject {
private int value;
public ImmutableObject(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
由于ImmutableObject不提供任何setter方法,并且成员变量value是基本数据类型,getter方法返回的是value的拷贝,所以一旦ImmutableObject实例被创建后,该实例的状态无法再进行更改,因此该类具备不可变性。
实际上Java平台类库中包含很多不可变的类,其中就有String
、基本类型的包装类、BigInteger
和BigDecimal
等等。同时在日常工作中,我们也有很多地方可以使用不可变对象,比如:家庭地址类、坐标类。还有Map
中的复合键。
2. 如何创建不可变对象
为了使类成为不可变,要遵循下面的几条规则:
(1)所有的成员变量都必须是private
,最好同时使用final
修饰。
(2)不提供修改原有对象状态的方法,比如setter
方法。
(3)通过构造器初始化所有成员变量,引用类型的成员变量必须进行深拷贝。
(4)getter
方法不能对外泄露this引用以及成员变量的引用。
(5)保证类不会被扩展,一般做法是声明这个类成为final
的。
(6)重写equals
和hashCode
方法。
下面我们仿造BigDecimal
自己写一个不可变对象类Complex
。代码如下:
public final class Complex {
private final double realPart;
private final double imaginaryPart;
public Complex(double realPart, double imaginaryPart) {
this.realPart = realPart;
this.imaginaryPart = imaginaryPart;
}
public double getRealPart() {
return realPart;
}
public double getImaginaryPart() {
return imaginaryPart;
}
public Complex plus(Complex complex) {
return new Complex(
getRealPart() + complex.getRealPart(),
getImaginaryPart() + complex.getImaginaryPart());
}
public Complex minus(Complex complex) {
return new Complex(
getRealPart() - complex.getRealPart(),
getImaginaryPart() - complex.getImaginaryPart());
}
public Complex times(Complex complex) {
double tempRealPart = getRealPart() * complex.getRealPart() - getImaginaryPart() * complex.getImaginaryPart();
double tempImaginaryPart = getRealPart() * complex.getImaginaryPart() + getImaginaryPart() * complex.getRealPart();
return new Complex(tempRealPart, tempImaginaryPart);
}
public Complex divided(Complex complex) {
double tempRealPart = getRealPart() * complex.getRealPart() + getImaginaryPart() * complex.getImaginaryPart();
double tempImaginaryPart = getImaginaryPart() * complex.getRealPart() - getRealPart() * complex.getImaginaryPart();
double temp = complex.getRealPart() * complex.getRealPart() + complex.getImaginaryPart() * complex.getImaginaryPart();
return new Complex(tempRealPart / temp, tempImaginaryPart / temp);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Complex complex = (Complex) o;
return Double.compare(complex.realPart, realPart) == 0 &&
Double.compare(complex.imaginaryPart, imaginaryPart) == 0;
}
@Override
public int hashCode() {
return Objects.hash(realPart, imaginaryPart);
}
@Override
public String toString() {
return "Complex{" +
"realPart=" + realPart +
", imaginaryPart=" + imaginaryPart +
'}';
}
}
这个类表示一个复数,除了提供标准的Object方法,它还提供了针对实部(realPart)和虚部(imaginaryPart)的访问方法,以及4种基本的算法运算:加法、减法、乘法和除法。需要注意的是这些算术运算都是返回一个新的Complex
对象,而不是修改原有对象。
3. 不可变对象的优缺点
3.1 优点
(1)不可变对象比较简单:不可变对象只有一种状态,即被创建时的状态。
(2)不可变对象本质上是线程安全的,它们不要求同步。当多个线程并发访问这样的对象时,它们不会遭到破坏。
(3)不可变对象可以被*地共享,我们应该充分利用这种优势,鼓励客户端尽可能地重现现有的实例。而要做到这一点,一个很简单的方法就是对于频繁用到的值,为它们提供共有的静态final
常量。BigDecimal
类中就有这样的案例:
public static final BigDecimal ZERO = zeroThroughTen[0];
public static final BigDecimal ONE = zeroThroughTen[1];
3.2 缺点
(1)对于每个不同的值都需要一个单独的对象,创建这些对象的代价可能很高,特别是大型的对象。