第39条:必要时进行保护性拷贝

Java是一门安全的语言,但是如果不采取措施,还是无法保证安全性。假设类的客户端会尽其所能来破坏类的约束条件,因此必须保护性地设计程序。

考虑下面的类,声称表示一段不可变的时间周期:

import java.util.Date;

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

因为Date本身是可变的,因此很容易违反起始时间不能在结束时间之后的约束。

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);//改变了终止时间,破坏类的约束条件

为保护Period实例的内部信息被破坏,对构造器的每个可变参数进行保护性拷贝,使用备份对象作为Period实例的组件,而不使用客户端传入的参数。

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(start + " after " + end);
}

保护性拷贝在参数有效性检查之前,这样做可以避免在多线程环境中,参数有效性通过后,从另外一个线程改变参数,使得参数有效性的检测失效。

不使用clone方法进行保护性拷贝的原因是Date是非final的,不能保证clone方法一定返回Date对象,它可能会返回转么处于恶意的不可信子类的实例。

进行了上面的工作后,改变Period实例仍有可能,因为它的访问方法提供了对其可变内部成员的访问能力:

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

改变访问方法,返回可变内部域的保护性拷贝:

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

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

采用新构造器和新访问方法后,Period真正地是不可变的了,不管程序员多么恶意和不合格,都绝对不会违反周期起始时间大于结束时间的约束。访问方法与构造器不同,在进行保护性拷贝的时候允许使用clone方法,因为Period内部Date的对象一定是Date,而不可能是某个不可信的子类。

只要有可能,应该使用不可变的对象作为对象内部组件,这样不必为保护性拷贝操心。在Period例子中,使用Date.getTime()返回的基本类型作为内部的时间表示法,就不需要进行保护性拷贝了。

如果类具有从客户端得到或者返回客户端的可变组件,类就必须保护性地拷贝这些组件,如果拷贝成本受到限制,并且类信任调用者不会修改内部组件,不进行保护性拷贝也是可以的,类的文档必须清楚说明这一点。

时间: 2024-10-15 09:24:19

第39条:必要时进行保护性拷贝的相关文章

第七章:方法。ITEM39:必要时进行保护性拷贝。

1 package com.twoslow.cha7; 2 3 import java.util.Date; 4 5 public final class Period { 6 7 private final Date start ; 8 private final Date end ; 9 10 public Period(Date start , Date end) { 11 if(start.compareTo(end) > 0) 12 throw new IllegalArgumentE

必要时进行保护性拷贝

假设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性的设计程序.demo: 1 import java.util.Date; 2 3 public final class Period { 4 private final Date start; 5 private final Date end; 6 public Period(Date start,Date end) { 7 if(start.compareTo(end) > 0){ 8 throw new IllegalArgum

第三十九条:必要时进行保护性拷贝

使Java使用起来如此舒适的一个因素在于,它是一门安全的语言.这意味着,它对于缓冲区溢出,数组越界,非法指针以及其他的内存破坏 都自动免疫,而这些错误却困扰着诸如C和C++这样的不安全语言.在一门安全的语言中,在设计类的时候,可以确切的知道,无论系统的 其他部分发生什么事情,这些类的约束都可以保持为真.对于那些"把所有的内存当成一个巨大的数组来看待"的语言来说,这是不可能的. 有一种情况进行保护性拷贝就是需要的: public  class   Period { private   f

23、保护性拷贝

程序有可能被恶意的使用,也就是遭受攻击,对于一个类来说,如果能够合适地使用保护性拷贝,将会使得类的安全性增大,例如: 1 class Person{ 2 private String name; 3 public Person(String name) { 4 this.name = name; 5 } 6 7 private Person getPerson() { 8 return new Person(name); 9 } 10 11 public void setName(String

39条常见的Linux系统简单面试题

1 如何看当前Linux系统有几颗物理CPU和每颗CPU的核数? 答:[[email protected] ~ 10:55 #35]# cat /proc/cpuinfo|grep -c 'physical id' 4 [[email protected] ~ 10:56 #36]# cat /proc/cpuinfo|grep -c 'processor' 4 2 查看系统负载有两个常用的命令,是哪两个?这三个数值表示什么含义呢? 答:[[email protected] ~ 10:56 #3

39条常见的linux系统管理面试题

1.如何看当前Linux系统有几颗物理CPU和每颗CPU的核数? 答:[[email protected] ~ 10:55 #35]# cat /proc/cpuinfo|grep -c 'physical id' 4 [[email protected] ~ 10:56 #36]# cat /proc/cpuinfo|grep -c 'processor' 4 2.查看系统负载有两个常用的命令,是哪两个?这三个数值表示什么含义呢? 答:[[email protected] ~ 10:56 #3

php优化必看的39条总结

1.如果一个方法可静态化,就对它做静态声明.速率可提升至4倍. 2.echo 比 print 快. 3.使用echo的多重参数(译注:指用逗号而不是句点)代替字符串连接. 4.在执行for循环之前确定最大循环数,不要每循环一次都计算最大值. 5.注销那些不用的变量尤其是大数组,以便释放内存. 6.尽量避免使用__get,__set,__autoload. 7.require_once()代价昂贵. 8.在包含文件时使用完整路径,解析操作系统路径所需的时间会更少. 9.如果你想知道脚本开始执行(译

[ZZ]39条更好的软件开发方法

1.重构是程序员的主力技能. 2.工作日志能提升脑容量. 3.先用profiler调查,才有脸谈优化. 4.注释贵精不贵多.杜绝大姨妈般的“例注”.漫山遍野的碎碎念注释,实际就是背景噪音. 5.普通程序员+google=超级程序员. 6.单元测试总是合算的. 7.不要先写框架再写实现.最好反过来,从原型中提炼框架. 8.代码结构清晰,其它问题都不算事儿. 9.好的项目作风硬派,一键测试,一键发布,一键部署; 烂的项目生性猥琐,口口相传,不立文字,神神秘秘. 10.编码不要畏惧变化,要拥抱变化.

《Effective Java》第7章 方法

第38条:检查参数的有效性 对于公有的方法,要用javadoc的@throws标签(tag)在文档中说明违反参数值限制时会抛出的异常.这样的异常通常为IllegalArgumentException, IndexOutOfBoundsException或NullPointerException. 非公有的方法通常应该使用断言(assertion)来检查它们的参数,具体做法如下所示: 第39条:必要时进行保护性拷贝 没有对象的帮助时,虽然另个类不可能修改对象的内部状态,但是对象很容易在无意识的情况