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

若为了实现表示不可变的时间周期,采用如下定义class的方法:

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 getStart() {
		return start;
	}
	public Date getEnd() {
		return end;
	}
	
	public static void main(String[] args) {
		Date start = new Date();
		Date end = new Date();
		Period p = new Period(start, end);
		end.setYear(89);
		System.out.println(p.getStart() +"-====="+p.getEnd());
		
		
	}
}
上述程序会输出:

Sat Oct 29 16:16:21 CST 2016-=====Sun Oct 29 16:16:21 CST 1989

开始时间大于结束时间,但是并没有抛出异常。出现这样的原因是因为:

final关键字修饰基本类型值是,表示其值是不变的,而用final修饰对象是,表示这个对象的地址是不变的,而其内容是可以修改的。

上边的程序如果在

this.start =start;
this.end = end;
后边从新对this.start 赋予其他地址值,例如 
this.start=end;
编译器会报错The final field start may already have been assigned”

上边的例子是由于Date是个可变类,Period的实例p保存的是start和end的地址(final不可以指向其他引用),但是客户端程序通过

end.setYear(89);
可以修改时间的年月日。

改进方法:

对于构造器的每个可变参数进行保护性拷贝(使用备份对象作为Period实例的组件),注意:拷贝必须在参数有效性检查之前:

public Period(Date start, Date end){
		this.start =new Date(start.getTime());
		this.end = new Date(end.getTime());	
		if (start.compareTo(end) >0) {
			throw new IllegalArgumentException(start +"after" + end);
		}

//		this.start =start;
//		this.end = end;	
		
	}
进行拷贝之后:即使对end进行修改,由于Period中得this.end与参数end不是同一个引用,所以此时并不能影响Period的end的时间。

修改后运行结果如下:

Sat Oct 29 16:34:25 CST 2016-=====Sat Oct 29 16:34:25 CST 2016

注意:对于构造方法的可变参数,不能使用clone方法进行保护性复制。

上述程序还有一个问题,我们可以通过getXX()方法修改实例.

客户端依然可以修改end进而修改Period实例。

如果mian方法改为:

public static void main(String[] args) {
		Date start = new Date();
		Date end = new Date();
		Period p = new Period(start, end);
		p.getEnd().setYear(77);
		System.out.println(p.getStart() +"-====="+p.getEnd());
		
		
	}
Sat Oct 29 16:41:43 CST 2016-=====Sat Oct 29 16:41:43 CST1977
对get方法进行保护性拷贝。修改如下

public Date getStart() {
		return new Date(start.getTime());
	}
	public Date getEnd() {
		return new Date(end.getTime());
	}
此时测试结果如下:

Sat Oct 29 16:44:45 CST 2016-=====Sat Oct 29 16:44:45 CST 2016

程序正常工作,客户端不可以修改Period实例。

整个过程示意图,如下:




保护性拷贝之前,client可以修改Period的实例




保护性拷贝之后,构造方法执行时,会复制一份给Period,进行新建对象



保护性拷贝之后,客户端想get获取时间,此时Period会复制一份新的给Client,Client的修改不会影响Period的实例







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值