java2集合框架的一些个人分析和理解(一)

2014-11-23 23:13:43 · 作者: · 浏览: 0
Java2中的集合框架是广为人知的,本文打算从几个方面来说说自己对这个框架的理解。
下图是java.util.Collection的类图(基本完整,有些接口如集合类均实现的Cloneable、Serializable没有包含进去)
QQ20140415173213_thumb5
我们常说要继承的话,到底是写个抽象类还是接口,它们区别在于:如果子类确实是父类的一种,应该使用抽象类,描述是“is-a”的关系,而接口则表示一种行为,描述的是“like-a”的关系。但在Java类库里,其实许多原则由于各种原因被打破了,比如在Collection框架里,List/Set都是Collection的一种,为什么不把Collection定义为抽象类呢?而ArrayList/LinkedList也都是List的一种,为什么不把List定义为抽象类呢?这就是原则和实际的折衷。作为Java类库而言,不仅要考虑面向对象的一些原则,也要考虑扩展性和语言本身的限制。能不能把Collection接口去掉,用AbstractCollection作为顶层?作为类库而言是不可以的,因为Java是单继承的,如果把AbstractCollection作为顶层,那么当用户自定义的类既要继承自己的父类,又要具备集合的属性,那么就做不到了(可以自定义集合接口,但就无法与Collection相互转化)。因此,Java集合框架采取的是类库广泛使用的接口+抽象类的形式,以同时获得接口和抽象类的好处,所以我们看到ArrayList extends AbstractList implements List(AbstractList本身就是实现List的,这里再写出implements List是为了使ArrayList的类结构更为清晰)。
另外我们再看Set接口,它的方法基本和Collection方法一模一样,为什么要再写一遍?一方面是作为类库而言要增加详细注释,虽然是同名的方法但实现的约束不同,比如Set的add方法是不会保存重复值的,另一方面是为了从Set接口本身能很清楚地看到它所提供的功能(比如size()方法,和Collection是完全一个含义,也重新定义了一遍),这是从类库易读性来考虑,对于我们自己编写的类,基本就不需要这样。
说多了,回到集合框架本身。
Iterable基本是个标识接口,同时约定了所有线性集合(数组、队列、栈这种一维的都属于线性集合,Map就属于二维,不要求遍历)必须是可以遍历的(集合要给出遍历结构),同时提供了配套的Iterator顶级接口,实现hasNext()、next()和remove()方法来完成遍历功能。为什么这里要定义remove接口方法却不定义add/set方法?个人觉得这可能是考虑在类库的使用过程中remove的频率更高,而add的方法频率要低,set的使用场景就更少了。
ListIterator相比Iterator就多提供了很多功能,包括上面提到的add/set,还有获得索引的nextIndex、previousIndex、以及往回迭代的hasPrevious()/previous()。给针对线性表的操作者更多的便利,事实上在AbstractList里就提供了iterator()和listIterator()两种方法来提供给开发者更多选择。相应的,在HashMap里头,也提供了实现Iterator接口的HashIterator内部抽象类,而在Apache Commons Collections下甚至单独写出MapIterator extends Iterator,由此可见,作为类库的设计者,在Iterator和ListIterator/HashMapIterator上是做了便捷性/易用性以及使用场景上的权衡的。
ArrayList内部结构是个数组,默认是10,在创建ArrayList对象时此数组是空的(Object[] EMPTY_ELEMENTDATA = {})只有当add的时候才扩容(如果扩容容量小于DEFAULT_CAPACITY,也就是10,就一次性扩容到10)。其扩容的机制是:当前数组容量已经无法放入更多元素的时候,增加原有数组的一半,
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
此数组的最大值是Integer.MAX_VALUE,也就是2^31-1。数组扩容的时候要考虑到当容量再度扩容一半的时候会越界,所以单独做了判断处理。数组扩容是在调用了本地方法去分配新的空间区域(下面是Arrays.CopyOf的代码)
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
System.arraycopy的代码如下
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
按照理论上来说,对于能预先知道数组大小的,应该在定义ArrayList的时候指定其容量以减少扩容次数,但是经过以下代码试验( 虚拟机64位Client模式,JDK1.7.0_45)
int times=1000000;
long startTime=new Date().getTime();
List arrayList=new ArrayList(times);
for(int i=0;i
arrayList.add(i);
}
long endTime=new Date().getTime();
System.out.println("ArrayList增加"+times+"次,耗费时间="+(endTime-startTime));
对于1百