读书人

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

发布时间: 2012-11-10 10:48:51 作者: rapoo

【第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

?

1 楼 lord_is_layuping 2012-09-25 copy on write就是一般不会复制,你改一个元素就修改一个,其它共享
http://en.wikipedia.org/wiki/Copy-on-write

如果不是immutable,可能经常要进行保护性拷贝
http://tonylian.iteye.com/blog/391256,保护性拷贝

保护性拷贝直接double了内存的占用
而且,如果在循环中进行保护性拷贝,那就不得了了

scala在后面折腾这些都是以性能为代价的。当然JVM本身也折腾,scala是进一步折腾 :-)

读书人网 >软件架构设计

热点推荐