Effective Java 学习笔记 需要时使用保护性拷贝

有这么一个类Period:

public 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;
    }
}

乍一看这个类是个不可变类,但实际上有两种方式可以修改其属性:

第一种:

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
start.setYear(66);

为了应对这种修改方式,可以采用在构造时保护性拷贝的办法:

public class Period {
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {
        // 这里对start和end进行保护性拷贝
        this.start = new Date(start);
        this.end = new Date(end);

        if (start.compareTo(end) > 0) {
            throw new IllegalArgumentException(start + "after" + end);
        }
    }

    public Date start() {
        return start;
    }

    public Date end() {
        return end;
    }
}

注意,之前是先判断start和end的有效性,再初始化变量;而现在改为先初始化,再判断有效性,其原因是——先判断有效性则可以在判断之后修改变量,即TOCTOU攻击。在初始化Period时,先传入有效的start和end,过了检查之后以及start和end初始化之前,在另外一个线程修改start和end,使其无效,然后Period的start和end就会出问题但不会被程序所捕捉到。因此要先初始化,再判断有效性。

此时,还有一种修改方法:

Date start = new Date();
Date end = new Date();
Period p = new Date(start, end);
p.end().setYear(66);

所以还应该在getter中加入保护性拷贝

public class Period {
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {
        // 这里对start和end进行保护性拷贝
        this.start = new Date(start);
        this.end = new Date(end);

        if (start.compareTo(end) > 0) {
            throw new IllegalArgumentException(start + "after" + end);
        }
    }

    public Date start() {
        return new Date(start);
    }

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

这就是完善好的Period类了。对于此类,还可以使用Instant等不可变类替代Date。

一般地,如果一个类有可变的组件,那么当从客户端获取和返回组件时,应该使用保护性拷贝。

然而,当对象的拷贝开销很大时,如果客户端不会不恰当地修改类的组件,那么可以考虑不使用保护性拷贝,但一定要在文档中说明。

一方面,程序中要避免创建冗余的对象,以减少资源占用;另一方面,在必须创建对象时,那就去创建,以保证程序的安全性和健壮性。


读《Effective Java》记录一下,留作以后参考。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值