以下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()可能返回子类对象,可能导致恶意攻击
保护性复制和不可变类一样都可能引起性能问题,如果不能使用保护性复制,同时又希望外部修改不引起属性变化,那么应在注释里详细说明,告诉调用者不要进行可能引起属性值改变的修改。