众所周知,java中的各种集合类,比如List,Set,Map等都是mutable的类,为了安全性,经常会利用到Collection类中的不可变包装的方法将其包装成不可变类型的变量,比如Collections.unmodifiableList()方法。
但是倘若进行如下代码,会发现尽管使用了该方法进行包装但是内部数据还是可以发生变化,于时,针对这个问题我展开了思考。
List<Date> dateList = new ArrayList<>();
Date date = new Date();
dateList.add(date);
System.out.println(dateList.get(0));
List<Date> unmodList = Collections.unmodifiableList(dateList);
unmodList.get(0).setTime(100);
System.out.println(unmodList.get(0));
System.out.println(dateList.get(0));
输出:
于时我发现尽管如此,unmodList中存放的Date对象也可以被改变。
于是我再次看了一下immutable类型的定义:
之后查看不可变包装的源码发现该函数返回一个UnmodifiableList类型变量
之后查看该类型的源码
然后发现UnmodifiableList类利用了装饰器模式将List类进行包装,其中变量list为final的引用,然后重写了List中的所有方法。
然后将List类中所有mutator方法都重写,抛出异常,其他非mutator方法则直接委派给list去做,依此来实现不可变包装。
在代码中继续进行实验。
List<Date> dateList = new ArrayList<>();
Date date = new Date();
dateList.add(date);
System.out.println(dateList.get(0));
List<Date> unmodList = Collections.unmodifiableList(dateList);
unmodList.get(0).setTime(100);
System.out.println(unmodList.get(0));
System.out.println(dateList.get(0));
System.out.println(unmodList==dateList);
System.out.println(unmodList.get(0)==dateList.get(0));
Date date1 = new Date();
dateList.set(0,date1);
System.out.println(unmodList.get(0));
输出为:
由此可以看出unmodList和dateList指向的不是一个内存空间,但是unmodList中的list指向的是dateList,也就是说dateList.get(i)与unmodList.get(i)指向的对象永远是同一个。
这样在dateList中进行修改的话也会导致看似不可变的unmodList发生变化。
所以要避免未包装过的引用泄露出去。
最后根据实验结果画出原来代码中
List<Date> dateList = new ArrayList<>();
Date date = new Date();
dateList.add(date);
System.out.println(dateList.get(0));
List<Date> unmodList = Collections.unmodifiableList(dateList);
unmodList.get(0).setTime(100);
相应的snapshot图如下:
由于那个包装类中只有一个final修饰的list引用指向被包装的类,所以应该是这样的
但是由于我不太清楚snapshot是否要考虑内部实现,所以也有可能是下面这样的画法: