Java防御性拷贝

Java防御性拷贝

原因

假定类的客户端,尽力摧毁类的不变量,破坏这个类的约束条件,因此你必须保护性的设计程序。

但更常见的是,你的类将不得不处理由于善意得程序员诚实错误而导致的意外行为。不管怎样,花时间编写在客户端行为不佳的情况下仍然保持健壮的类是值得的。

举例

考虑以下类,表示一个不可变的时间期间:

// 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
}

这个类看上去没有什么问题,时间是不可改变的。然而Date类本身是可变的。

// Attack the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);  // Modifies internals of p!

解决方案

为了保护Period实例的内部信息避免受到修改,导致问题,对于构造器的每个可变参数进行保护性拷贝(defensive copy)是必要的:

// 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);
}

有了新的构造方法后,前面的攻击将不会对Period实例产生影响。

还请注意,我们没有使用Date的clone方法来创建防御性拷贝。因为Date是非final的,所以clone方法不能保证返回类为java.util.Date的对象,它可以返回一个不受信任的子类的实例,这个子类是专门为恶意破坏而设计的。例如,这样的子类可以在创建时在私有静态列表中记录对每个实例的引用,并允许攻击者访问该列表。这将使攻击者可以自由控制所有实例。为了防止这类攻击,不要使用clone方法对其类型可由不可信任子类化的参数进行防御性拷贝。

虽然替换构造方法成功地抵御了先前的攻击,但是仍然可以对Period实例进行修改,因为它的访问器提供了对其可变内部结构的访问:

// Second attack on the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78);  // Modifies internals of p!

为了抵御第二次攻击,只需修改访问器以返回可变内部字属性的防御性拷贝:

// Repaired accessors - make defensive copies of internal fields
public Date start() {
    return new Date(start.getTime());
}

public Date end() {
    return new Date(end.getTime());
}

在内部组件返回给客户端的时候,也要考虑是否可以返回一个指向内部引用的数据。或者,不使用拷贝,你也可以返回一个不可变对象。如:Colletions.unmodifiableList(List<? extends T> list)

如果类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性的拷贝这些组件。如果拷贝的成本受到限制,并且类信任他的客户端不会进行修改,或者恰当的修改,那么就需要在文档中指明客户端调用者的责任(不的修改或者如何有效修改)。

总结

总之,如果一个类有从它的客户端获取或返回的可变组件,那么这个类必须防御性地拷贝这些组件。如果拷贝的成本太高,并且类信任它的客户端不会不适当地修改组件,则可以用文档替换防御性拷贝,该文档概述了客户端不得修改受影响组件的责任。

参考链接:https://www.jianshu.com/p/0da637139be4

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值