【笔记50】必要时进行保护性拷贝

JAVA是一门安全的语言。这就意味着,它对缓冲区溢出、数组越界、非法指针以及其它的内存破坏错误都自动免疫,而这些错误却困扰着诸如C和C++这样的不安全的语言。在一门安全的语言中,可以确切的知道,无论系统的其他部分发生什么事,这些类的约束都可以保持为真。对于那些“把内存当做一个巨大数组看待”的语言来说,这是不可能的。

假设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性的设计程序。考虑下列类,它声称可以表示一段不可变的时间周期:

public final class Period {
    private final Date start;
    private final Date end;
    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 period = new Period(start, end);
end.setYear(78);
System.out.println(period.end());

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

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实例不再有效。保护性拷贝是在检查参数合法性之前进行的,而且参数的合法性的检查是针对保护性拷贝之后的对象,而不是针对原始对象。这样做是为了避免危险阶段来自另外一个线程的修改参数。危险阶段指的是检查参数开始,直至保护性拷贝参数之间的时间。在计算机安全社区中,这段时间被称为TOC、TOU(Time-Of-Use,Time-Of-Check)。

对于参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝。
虽然替换构造器就可以成功地避免上述的攻击,但是改变Period实例仍然是有可能的,因为它的访问方法提供了对其可变内部成员的访问能力:

//Second attack on the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
end.setYear(78);
System.out.println(period.end());
 

为了防止二次攻击,可以让end()返回拷贝对象。

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

采用了新的构造器和新的访问方法之后,Period真正是不可变的了。

参数的保护性拷贝不仅仅针对不可变类。每当编写方法和构造器时,如果他要允许客户提供的对象进入到内部数据结构中,则有必要考虑一下,客户提供的对象是否有可能是可变的,我是否能够容忍这种可变性。特别是你用到list、map之类连接元素时。

如果类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性的拷贝这些组件。如果拷贝的成本受到限制,并且类信任他的客户端不会进行修改,或者恰当的修改,那么就需要在文档中指明客户端调用者的责任(不的修改或者如何有效修改)。特别是当你的可变组件的生命周期很长,或者会多层传递时,隐藏的问题往往暴漏出来就很可怕。

当我们准备实现一个具有特殊约束条件类的时候,假设类的客户端会尽其所能的破坏这个类的约束条件,因此我们必须保护性设计程序。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值