可变类与不可变类的区别
??? }
??? }
??? 假定Person类的name属性定义为Name类型:
??? public class Person{
??? private Name name ;
??? private Gender gender;
??? …
??? }
以下代码创建了两个Person对象,他们的姓名都是“王小红”,一个是女性,另一个是男性。在最后一行代码中,把第一个Person对象的姓名改为“王小虹”。
??? Name name=new Name("小红","王");
??? Person person1=new Person(name,Gender.FEMALE);
??? Person person2=new Person(name,Gender.MALE);
??? name=new Name("小虹","王");
??? person1.setName(name); //修改名字
??? 与不可变类对应的是可变类,可变类的实例属性是允许修改的。如果把以上例程11-9的Name类的firstname属性和lastname属性的 final修饰符去除,并且增加相应的public类型的setFirstname()和setLastname()方法,Name类就变成了可变类。以下程序代码本来的意图也是创建两个Person对象,他们的姓名都是“王小红”,接着把第一个Person对象的姓名改为“王小虹”:
??? //假定以下Name类是可变类
??? Name name=new Name("小红","王");
??? Person person1=new Person(name,Gender.FEMALE);
??? Person person2=new Person(name,Gender.MALE);
??? name.setFirstname(" 小虹"); //试图修改第一个Person对象的名字
??? 以上最后一行代码存在错误,因为它会把两个Person对象的姓名都改为“王小虹”。由此可见,使用可变类更容易使程序代码出错。因为随意改变一个可变类对象的状态,有可能会导致与之关联的其他对象的状态被错误地改变。
??? 不可变类的实例在实例的整个生命周期中永远保持初始化的状态,它没有任何状态变化,简化了与其他对象之间的关系。不可变类具有以下优点:
??? l 不可变类能使程序更加安全,不容易出错。
??? l 不可变类是线程安全的,当多个线程访问不可变类的同一个实例时,无须进行线程的同步。
??? 由此可见,应该优先考虑把类设计为不可变类,假使必须使用可变类,也应该把可变类尽可能多的属性设计为不可变的,即用final修饰符来修饰,并且不对外公开用于改变这些属性的方法。
??? 在创建不可变类时,假如它的属性的类型是可变类型,在必要的情况下,必须提供保护性拷贝,否则,这个不可变类实例的属性仍然有可能被错误地修改。这条建议同样适用于可变类中用final修饰的属性。
??? 例如例程11-10的Schedule类包含学校的开学时间和放假时间信息,它是不可变类,它的两个属性start和end都是final类型,表示不允许被改变,但是这两个属性都是Date类型,而Date类是可变类。
??? 例程11-10 Schedule.java
??? import java.util.Date;
??? public final class Schedule{
??? private final Date start; //开学时间,不允许被改变
??? private final Date end; //放假时间,不允许被改变
??? public Schedule(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;}
??? }
??? 尽管以上Schedule类的start和end属性是final类型的,但由于它们引用Date对象,在程序中可以修改所引用Date对象的属性。以下程序代码创建了一个Schedule对象,接下来把开学时间和放假时间都改为当前系统时间。
??? Calendar c= Calendar.getInstance();
??? c.set(2006,9,1);
??? Date start=c.getTime();
??? c.set(2007,1,25);
??? Date end=c.getTime();
??? Schedule s=new Schedule(start,end);
??? end.setTime(System.currentTimeMillis()); //修改放假时间
??? start=s.getStart();
??? start.setTime(System.currentTimeMillis()); //修改开学时间
??? 为了保证Schedule对象的start属性和end属性值不会被修改,必须为这两个属性使用保护性拷贝,参见例程11-11。
??? 例程11-11 采用了保护性拷贝的Schedule.java
??? import java.util.Date;
??? public final class Schedule {
??? private final Date start;
??? private final Date end;
??? public Schedule(Date start,Date end){
??? //不允许放假日期在开学日期的前面
??? if(start.compareTo(end)>0)throw new IllegalArgumentException(start +" after " +end);
??? this.start=new Date(start.getTime()); // 采用保护性拷贝
??? this.end=new Date(end.getTime()); // 采用保护性拷贝
??? }
??? public Date getStart(){return (Date)start.clone() ;} // 采用保护性拷贝
??? public Date getEnd(){return (Date)end.clone() ;} // 采用保护性拷贝
??? }
??? 通过采用保护性拷贝,其他程序无法获得与Schedule对象关联的两个Date对象的引用,因此也就无法修改这两个Date对象的属性值。