【第24条】需要时使用保护性拷贝

    Java受欢迎的一个重要原因是它是一门安全的语言。它对于缓冲区溢出、数组越界、非法指针以及其他内存破坏错误自动免疫。

 

    但是,这并不是说你可以高枕无忧,正如前面【第5条】中所述的,某些情况下你还是要自行回收过期引用的。现在我们再来说一下你不得不做的“自我防卫”性工作。

 

    【第5条】中的回收过期引用,即使你没有这么做,顶多是浪费一些内存资源。但是,如果本条所述的“自我防卫”工作你没有到位的话,那后果就可能是灾难性的了,而其错误所在往往不容易被发现。

 

     如果一个方法或构造函数允许可变对象进/出,那么就要考虑一下使用者是否有可能改变它。如果是的话,那你必须对该对象进行保护性拷贝,使进入方法内部的对象是外部时的拷贝而不它本身(因为外部的对象有可能还会被改变)。

 

public class Stuff {
    private String name;
    private Date birthday;

    public Stuff(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public Date getBrithday() {
        return this.birthday;
    }
}

 

这样的一个职员类,它包括姓名和生日。在没有进行保护性拷贝的保护之前,我们来看看攻击者(攻击者太极端了,很肯能是一个“高级”程序员)是如何开始他的破坏工作的:

 

Date day1 =  new Date(1970,1,1)
Stuff zhangSan = new Stuff("张三", day1);
// .......

// 几行其他代码之后,老先生有想起 day1 来了
day1.setYear(2009);

// 这下坏了,张三先生成了婴儿了,zhangSan.birthday = 2009-1-1

 

这样的原因就是Date型是一个可变类型(Java早期遗留下来的遗憾之一,详见【第13条】)。我们使用保护性拷贝来防之:

 

    public Stuff(String name, Date birthday) {
        this.name = name;
        this.birthday = new Date(birthday.getTime());   // 内部的birthday实际上在这里新创建的一个“日期值”等于实参的新实例
     // 因为,Date型是可变类型,所以“值”虽相等,也是新实例
    }

 

现在这位老先生无论怎么折腾 day1 也不会影响到 张三 大哥的生辰八字了。但是,人家老先生还有高招:

 

zhangSan.getBirthday().setYear(2010);
// 这回张三先生就更惨了,直接回娘胎了

 

如果getBrithday方法是我们必须提供的,那该怎么办呢?答案还是保护性拷贝:

 

    public Date getBrithday() {
        return (Date) this.birthday.clone();  // 我造个新的实例扔给你,你随便折腾吧,影响不到我
    }

 看到了吧,这就是保护性拷贝,使用构造函数也好,克隆也罢,总之是得到一个原对象的副本。

 

    最后要提一下包装类模式(包括适配器模式和装饰模式),根据包装类的本质特征,使用者只需直接访问被包装的对象,就可以破坏包装类的约束条件,但是,这样做的前提是确保不会伤害使用者自己。

 

 

 

【Effective Java 学习笔记】系列连载专题请见:
http://tonylian.iteye.com/categories/64208

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值