若为了实现表示不可变的时间周期,采用如下定义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的实例