错误示例
如下为Period类,未提供set方法,看起来无法修改其内部属性
final class Period {
private final Date start;
private final Date end;
Period(Date start, Date end) {
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + " > " + end);
}
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
}
但实际上,Date本身是可变的,对Date的修改会导致对Period的修改
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
end.setYear(7);
Java8之后应该使用Instant、LocalDateTime、ZonedDateTime代替,但对于可变对象域(即非final类),应该对其进行拷贝
正确示例
需根据传进来的参数创建新的实例,同时创建应在检查之前,从而避免在检查-创建期间参数发生变化,同时这里不调用clone,因为可能传进来的是Date的子类(通过复写clone恶意攻击)
final class Period {
private final Date start;
private final Date end;
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 + " > " + end);
}
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
}
虽然修改了构造方法,但其get()方法仍然给了外面修改可变内部成员的能力,如下
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
period.getEnd().setYear(7);
所以也要对get()方法的返回对象进行拷贝,get方法可以使用clone,因为可以确定内部对象为Date,而不是其子类
final class Period {
private final Date start;
private final Date end;
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 + " > " + end);
}
}
public Date getStart() {
return new Date(start.getTime());
}
public Date getEnd() {
return new Date(end.getTime());
}
}
需注意:长度非零的数组始终是可变的,需调用clone返回拷贝对象或返回不可变视图,如下
class Test {
private static final Integer[] VALUES = {1, 2, 3};
public static final List<Integer> UNMODIFIABLE_VALUES = Collections.unmodifiableList(Arrays.asList(VALUES));
public static final Integer[] values() {
return VALUES.clone();
}
}