实现不可变类

1.      什么是不可变类

不可变类是指一旦通过构造器初始化一个类的对象状态后,在这个对象的生命周期内,它的内部状态是不能改变的。例如java中的string、integer对象等等。

2.      为什么要设计不可变了,类不可变的好处

(1)    不可变类是线程安全的。

因为不可变类的对象的状态在构造器中一次设置完成后,在这个对象的整个生命周期内都不能改变它的内部状态了,所以在不同线程中可以安全的共享不可变类的对象(因为不同线程都不能修改不可变类的状态,所以就不需要同步了)。

(2)    不可变对象可以安全的作为散列的键值

每个对象都是独一无二的,不可变类的对象构造完成后,域是不能变得。所以hashcode就固定下来了。

以下例子可以说明不可变对象在散列存储结构中的使用。

可变对象:

public class StringHolder {

   

    private String s ;

   

    publicStringHolder(String s){

       this.s = s;

    }

 

    @Override

    public boolean equals(Object obj){

       if(obj == this)

           return true;

       if(!(obj instanceofStringHolder))

           return false;

       StringHolderh = (StringHolder) obj;

       return h.s.equals(s);

    }

   

    public void setString(Strings){

       this.s = s;

    }

   

    public String toString(){

       return s;

    }

 

    @Override

    public int hashCode() {

       return (s != null) ? s.hashCode() : 0;

}

         把这个可变类的对象,放入HashSet中,然后再去改变可变对象域的值。会使得HashSet的行为不确定。

StringHolder h = new StringHolder("hua");

       System.out.println(h.hashCode());

       Set<StringHolder> set = new HashSet<StringHolder>();

       set.add(h);

       h.setString("Hua");

      

       System.out.println(h.hashCode());

      

       System.out.println(set.contains(h));--false,明明在HashSet中放了h

       但是返回false

       System.out.println(set.size());--返回1

       System.out.println(set.iterator().next());

 

3.      设计不可变类的步骤

(1)    用final来修饰所有的域

(2)    不要提供可以修改域的方法(modify method)

(3)    类用final修饰,也就是说不可以子类化

这是为了防止子类破坏不变性约束

(4)    使得所有的域都是private的。

(5)    保证对可变域的访问都是互斥的

如果不可变中有有域指向的是一个可变的对象,那么必须确保client端不能得到可变对象的引用,阻止client端修改可变对象。

如何保证:

使可变域private

   除了在构造器中对可变域初始化后,不要再其他地方修改可变域的值

   在方法中不要暴露可变域的引用

         对可变域要进行保护性拷贝,只有这样才能真正使得一个类的对象在生命周期中不可变。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

保护性拷贝

1.      不可变类中如果有可变类的引用的话,需要做保护性拷贝

public final class Period {

   

    private final Date start;

    private final Date end;

   

    public Period(final Date start,final 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 void checkV() {

       if(start.compareTo(end) > 0)

           throw new IllegalArgumentException(start + "after" + end);

    }

Period类是不可变类吗?粗看貌似是不变类,约束在构造器中定义了。

但是我们这么用Period却能轻易改变Period类的内部状态。

Date start = new Date();

       Date end = new Date();

      

       Period p = new Period(start,end);

       start.setMonth(11);

       p.checkV();  

问题在于Date类的对象是个可变的对象,我们域中的start引用直接指向了与client端new的同一个Date对象,Period不能阻止client端去修改域start指向的对象。

(1)构造器中传入一个可变对象时,我们需要做一些保护性的copy。

public Period(final Date start,final 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);

      

    }

(2)    方法中返回对象的时候也要做保护性copy;反正不变类中的域和client端使用的对象,不能是同一个对象,也就说说不变类中的可变对象不能对client端逸出。

Date start = new Date();

       Date end = new Date();

      

       Period p = new Period(start,end);

       P.getStartDate().setMonth(2013);

       p.checkV();

修正getStartDate():

public Date getStart() {

       return newDate(start.getTime());

    }

                                                                  

不可变类中有可变的数组

1.      不用使用clone方法来copy数组,不好

2.      用java.util.Arrays中copyOf复制一个新的数组。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值