欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-09 13:33:55
...

    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

 

相关标签: 工作