7.39 必要时使用保护性复制(defensive copy)

以下Period类用于表示两个日期之间的间隔:

 

import java.util.Date;

public class Period {
	private Date start;
	private 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 boolean isValid(){
		return start.compareTo(end)<=0;
	}

	public Date getStart() {
		return start;
	}

	public Date getEnd() {
		return end;
	}
}

 

 

以下测试将失败:

	@Test
	public void testPeriod(){
		Date start=new Date();
		Date end=new Date();
		
		Period period=new Period(start, end);
		Assert.assertTrue(period.isValid());
		
		
		end.setYear(1);//修改了传给Period的参数值
		Assert.assertTrue(period.isValid());
		
		period.getEnd().setYear(1);////修改了Period返回的属性值
		Assert.assertTrue(period.isValid());
	}

 

 

如果要求Period的属性start、end对外部是不可变的(即只有Period内部可以修改此属性值,外部修改不会对属性值有任何影响),那么需要对传入的可变参数值和返回的属性值(即start,end)进行保护性复制,修改后的Period如下:

 

public class Period {
	private Date start;
	private Date end;
	
	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");
	}
	
	public boolean isValid(){
		return start.compareTo(end)<=0;
	}

	public Date getStart() {
		//返回复制的对象
		return new Date(start.getTime());
	}

	public Date getEnd() {
		//返回复制的对象
		return new Date(end.getTime());
	}
}

 

 

另一种修改方式如下:

 

public class Period {
	//使用不可变的数据类型
	private long start;
	private long end;
	
	public Period(Date start,Date end){
		this.start=start.getTime();
		this.end=end.getTime();
		
		if(this.start>this.end)
			throw new IllegalArgumentException("start after end");
	}
	
	public boolean isValid(){
		return start<=end;
	}

	public Date getStart() {
		return new Date(start);
	}

	public Date getEnd() {
		return new Date(end);
	}
}

 

 

如果不希望外部修改引起类的属性的变化,但是属性的数据类型又是Date,Array,Collection,Map等可变类型,那么应使用保护性复制,使用原则如下:

1.在校验输入参数前进行保护性复制

2.如果属性类型是可变的,在返回给调用者前应进行保护性复制

3.不要使用clone()进行保护性复制,clone()可能返回子类对象,可能导致恶意攻击

 

保护性复制和不可变类一样都可能引起性能问题,如果不能使用保护性复制,同时又希望外部修改不引起属性变化,那么应在注释里详细说明,告诉调用者不要进行可能引起属性值改变的修改。

 

 

 

 

 

 

 

 

你可能感兴趣的:(copy)