Guava源码分析——Immutable Collections(1)
在Java中,conrrent包提供了很多线程安全的集合,但有的时候我们可以换一种方式对思考使用线程安全集合,Guava的Immutable提供了一系列不可变集合类型,不可变就使得集合成为了常量,常量必然线程安全。对于集合的不可变,除了Guava提供的Immutable Collections以外,还是有Collections.unmodifiableCollection(),而两者之间,还是有些区别的。从UML图中,可以看出,ImmutableCollection继承了AbstractCollection,从而也成为了Java标准的集合类。与标准集合相同,ImmutableCollection分别被继承为三种类型集合——List、Set、MultiSet。
与Collections.unmodifiableXXX有以下几种不同:
- 防御性copy,无论原集合怎样改变,经过ImmutableCollections.copyOf()方法返回的集合,无论原集合怎样变化,新集合都不会在变化,而Collections.unmodifiableCollection()与之相反。如下代码:
@Test public void testCopy() { List<Integer> numbers = Lists.newArrayList(1, 2, 3, 4); List<Integer> integers1 = ImmutableList.copyOf(numbers); List<Integer> integers2 = Collections.unmodifiableList(numbers);
numbers.add(0, -1); Assert.assertEquals(1, integers1.get(0).intValue());//Pass Assert.assertEquals(1, integers2.get(0).intValue());//Failure }
2. Collections.unmodifiableCollection()修饰后的集合,仍然具有原集合的特性,而不是将集合转化为常量
@Test
public void testConstruct() { Set<Integer> numbers = Sets.newConcurrentHashSet(); Set<Integer> integers1 = Collections.unmodifiableSet(numbers);//生成不可变集合
//虽然集合已经不可变,但仍然会在并发读取的时候发生CAS操作,不可变意味着线程安全,而原集合的CAS多此一举。
for (Integer integer : integers1) {
System.out.println(integer);
}
}
3. 对于空间使用的节省,后面builder源码分析时候,会说到
ImmutableCollections类中将所有write方法都置为throw new UnsupportedOperationException()操作,这里需要说明的是抽象类ImmutableCollection.Builder
Guava提供了构造器方式的构造不可变集合,如下代码所示:
public void testBuilder() { ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<Integer>();
//绝对不要这样做,初始size为4,超过后,每次加入扩容新加入集合的个数,下面写法每次扩容1,后续每次都会导致扩容copy发生
ImmutableList<Integer> in = builder.add(1).add(2).add(3).add(4)
.add(5)//超过初始size,扩容copy发生,size变为5
.add(6)//超过size,扩容copy发生
.build();
//只扩容一次
ImmutableList<Integer> in2 = builder.add(1, 2, 3, 4, 5, 6).build();
}
ArrayBasedBuilder是ImmutableList.Builder和ImmutbleSet.Builder的底层实现,数组形式,每次扩容(初始化size为4)。
abstract static class ArrayBasedBuilder<E> extends ImmutableCollection.Builder<E> { Object[] contents; int size; ArrayBasedBuilder(int initialCapacity) { checkNonnegative(initialCapacity, "initialCapacity"); this.contents = new Object[initialCapacity]; this.size = 0; } /** * Expand the absolute capacity of the builder so it can accept at least * the specified number of elements without being resized. */ private void ensureCapacity(int minCapacity) { if (contents.length < minCapacity) { this.contents = ObjectArrays.arraysCopyOf(
//每次扩容到contents.size,如Collections.unmodifiableList(new ArrayList<T>)()每次扩容一半不同,不会存在空间浪费 this.contents, expandedCapacity(contents.length, minCapacity)); } } @Override public ArrayBasedBuilder<E> add(E element) { checkNotNull(element); ensureCapacity(size + 1); contents[size++] = element; return this; } }
Immutable可以作为常量来使用,相信大家在自己的项目中肯定会有这样的需求。
- 初始化集合作为筛选用,黑名单功能
- 防止返回的集合引用,被他人误用,修改原集合,导致bug出现