不可变集合
一、不可变对象
对象的不可变形式是安全的:
1)在多线程条件下不存在竞态问题
2)不可变集合不需要考虑变化,节省空间和时间。不可变集合比可变形式有更好的内存利用率
3)不可变集合常做常量容器
创建对象的不可变拷贝是一种有效的防御性编程技巧,可以避免原来假想为不可变的对象被误改。
以ImmutableList 为例:
List<String> list = Lists.newArrayList();
list.add("FULL");
list.add("LOW");
final List<String> unmodifiableList = Collections.unmodifiableList(list);
ImmutableList<String> copiedImmutableList = ImmutableList.copyOf(list);
System.out.println(list);
System.out.println(unmodifiableList);
System.out.println(copiedImmutableList);
list.add("NORMAL");
System.out.println(list);//[FULL, LOW, NORMAL]
/* 即使是包装后的 unmodifiableXX 维护的其实不是真正“不可变” 的元素*/
System.out.println(unmodifiableList); //[FULL, LOW, NORMAL]
/* copy 后的List 是不可变的 ,对原list的修改不会反应到copy后的List上来 */
System.out.println(copiedImmutableList); //[FULL, LOW]
二、UnmofiableList与ImmutableList
java.util.Collections#unmodifiableList(List< ? extends T> list )对List的包装得到的 UnmodifiableList 并不是真正的“不可变”,JDK仍然提供了unmodifiableList 的add()、addAll()等增删改数据的API。(但是这些 API提供了也没有卵用,add后将会抛出java.lang.UnsupportedOperationException 异常)。但对list的add()将会反映到unmodifiableList 上来。
List<String> list = Lists.newArrayList("green", "red");
List<String> unmodifiableList = Collections.unmodifiableList(list);
// unmodifiableList.add("black");
// will throw java.lang.UnsupportedOperationException
list.add("black"); // Ok
System.out.println(unmodifiableList); //will print out words containing "black"
可见:虽然JDK也提供了Collections.unmodifiableXXX方法把集合包装为不可变形式,但
- 笨重而且累赘:不能舒适地用在所有想做防御性拷贝的场景;
- 不安全:要保证没人通过原集合的引用进行修改,返回的集合才是事实上不可变的(最重要原因)
- 低效:包装过的集合仍然保有可变集合的开销,比如并发修改的检查、散列表的额外空间,等等。
总结:不可变集合的场景:::如果你没有修改某个集合的需求,或者希望某个集合保持不变时,把它防御性地拷贝到不可变集合是个很好的实践。
三、最佳实践
为了防止有人生硬地为不可变对象重新赋值,建议定义不可变集合为final变量:
final ImmutableList<String> copiedImmutableList = ImmutableList.copyOf(list);
copiedImmutableList = ImmutableList.of("red");
需要注意的是:所有Guava不可变集合的实现都不接受null值。
API: 构造不可变集合的方法:
- copyOf方法,如ImmutableSet.copyOf(set);
- of方法,如ImmutableSet.of(“a”, “b”, “c”)或 ImmutableMap.of(“a”, 1, “b”, 2);
- Builder工具(推荐)
final ImmutableList<String> immutableList = ImmutableList.<String>builder()
.add("green")
.addAll(Lists.newArrayList("red", "black"))
.build();
final ImmutableMap<String, Object> immutableMap = ImmutableMap.<String, Object>builder()
.put("a", "1")
.putAll(Maps.newHashMap())
.build();
- 对有序不可变集合来说,排序是在构造集合的时候完成的,如
ImmutableSortedSet.of(“a”, “b”, “c”, “a”, “d”, “b”)。既然不可变,肯定不能在原集合上排序,因为这样会改变原有的元素位置。
四、其余类型
可变集合接口 | 属于JDK还是Guava | 不可变版本 |
---|---|---|
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
SortedSet/NavigableSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
SortedMap | JDK | ImmutableSortedMap |
Multiset | Guava | ImmutableMultiset |
SortedMultiset | Guava | ImmutableSortedMultiset |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
ClassToInstanceMap | Guava | ImmutableClassToInstanceMap |
Table | Guava | ImmutableTable |