提示五十:必要时进行保护性拷贝

提示五十:必要时进行保护性拷贝。

愉快使用 Java 的原因,它是一种安全的语言。 这意味着在缺少本地方法的情况下,它不受缓冲区溢出,数组溢出,野指针以及其他困扰 C 和 C++ 等不安全语言的内存损坏错误的影响。 在一种安全的语言中,无论系统的任何其他部分发生什么,都可以编写类并确切地知道它们的不变量会保持不变。 在将所有内存视为一个巨大数组的语言中,这是不可能的。

// Broken "immutable" time period class
public final class Period {
    private final Date start;
    private final Date end;
    /**
    * @param start the beginning of the period
    * @param end the end of the period; must not precede start
    * @throws IllegalArgumentException if start is after end
    * @throws NullPointerException if start or end is null
    */
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(start + " after " + end);
        this.start = start;
        this.end = end;
    }
    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }
    ... // Remainder omitted
}

书中给出了这样一个例子,看上去start,end都是final类型的变量,在构造函数的时候也做了校验,应该没有什么问题。但是因为 Date 类本身是可变的,因此很容易违反这个约束条件。从 Java 8 开始,解决此问题的显而易见的方法是使用 Instant(或 LocalDateTime 或 ZonedDateTime)代替 Date,因为 Instant(和其他 java.time 类)是不可变的。

但是老代码依然还在那里,甚至已经被大量使用。为了避免这种问题,可以对于构造器的每个可变参数进行保护性拷贝(defensive copy)并且使用备份对象作为 Period 实例的组件,而不使用原始的对象。

// Repaired constructor - makes defensive copies of parameters
public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    if (this.start.compareTo(this.end) > 0)
        throw new IllegalArgumentException(this.start + " after " + this.end);
}

// Repaired constructor - makes defensive copies of parameters
public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    if (this.start.compareTo(this.end) > 0)
        throw new IllegalArgumentException(this.start + " after " + this.end);
}

注意,保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。并且对于参数类型可以被不可信任方子类化的参数,不要使用 clone 方法进行保护性拷贝

我们系统中其实也大量存在这样的问题,主要平时开发的时候没有这种思维,不会想着时刻保护自己的代码。Guava中有着一套ImmutableCollection,并且有着很好用的copy工具,如果想要保护参数应该很好用。

你可能感兴趣的:(提示五十:必要时进行保护性拷贝)