学习ArrayList,首先一般这个应该是从上往下的,上层定义规则,下层是具体实现,越上层特征越明显,所以应该先清楚ArrayList的继承体系。ArrayList继承AbstractList,实现List,RandomAccess,Cloneable,Serializable,关键是List和AbstractList,List继承Collection,AbstractList继承AbstractCollection,实现Colletion。具体关系如下图:
在学习ArrayList的时候,先要明白什么是Colletion,具有那些特征和能力,List在Colletion的基础上添加了哪些特性,ArrayList是如何具体实现的。AbstractColletion和AbstractList没有具体实现,充当了什么样的角色,一层一层往下剥吧。
Collection
Collection是用于存储一组元素的对象,存储方式可以是有序,也可以是无序的,里面的元素可以重复,也可以不重复。所以就衍生出了List,Set等具有某些特性的容器。
容器具有哪些特性,容器的作用是存储,可以想象它的功能,添加元素,删除元素,查看元素,判断元素是否存在,遍历,那Colletion里面也差不多就是这些方法了。
Colletion接口里面的方法大致有:
增加:add,addAll
删除:remove,removeAll,removeIf,retainAll,clear
查看:size
判断:isEmpty,contains,containsAll,
遍历:iterator,Colletion继承了Iterable接口,Iterable主要就是返回一个用于遍历的Iterator接口的对象,还有提供了加强型的for循环,就是说必须为容器提供一个可以用于遍历的Iterator实现。
转换:两个toArray方法,用于转换为数组。
Colletion描述了一个容易基本的样子,也不具备什么特征。
AbstractColletion
看到AbstractXXX的时候,首先这是抽象类,没有具体实现,类似与模板方法,将具体实现延迟到子类,提供一些模板方法。size,add,Iterator需要具体实现,然后借助这三个方法实现其余的方法,toArray方法主要是Arrays.cory实现,Arrays.cory通过system.copyarray实现,hugeCapacity方法主要是判断是否已达到最大容量,如果超过Integer.MAX_VALUE则抛出异常。
List
List特点就是有序的,所以在Colletion的基础上,他所有的特征都是围绕有序展开的,就是每一个元素都有自己的序号,方法代表一个对象的能力和特性,List相对于Collection增加的方法也是围绕这一个特性展开的。
增加了一个可以根据位置添加元素的add方法,以及一个addAll方法
根据位置获取元素的get方法
根据元素位置的index和lastIndex方法
根据位置替换元素的set方法
还有就是提供双向遍历的ListIterator方法
因为是List是有序的,添加了一个sort方法
根据起始位置的subList方法
AbstractList
功能类似与AbstractColletion,构造器类型为protect,那么在非子类,想要new它的时候,就要实现它。
具体操作的size,add,set,remove,get方法还是延迟到具体实现中。
和AbstractCollection不同的是,Iterator的实现,为什么AbstractCollletion没有,而这里有,在看完其他类的Iterator实现之前,我不知道,但是Iterator的实现是需要和聚合对象本来操作方法相关联的,应该需要根据具体类型的特征来实现,List的特点是有序,这里实现Iterator主要是使用了cursor,lastRet,游标和上一次遍历的位置,通过size,get,remove方法来实现hasNext,next,remove方法,在延迟加载的情况下,是可以提供实现的。自己也可以根据需要自定义Iterator,提供方法返回这个Iterator,就可以使用了。
ListIterator继承Iterator的实现,增加向前遍历的功能。无惨的ListIterator是有参的ListIterator的特例,为什么Iterator不提供参数,而这里有,因为ListIterator是双向遍历的,参数的作用就是控制cursor的值。
新增一个modCount属性,通过和expectedModCount的比较,用于检测并发修改的情况。//TODE可以模拟一个并发的场景,加深理解。
subList方法,其实这个SubList还是List本身,只不过是加了前后界限。通过代码可以发现,将fromIndex复值给offset,在原有List上面发生偏移,toIndex-fronIndex为size,进行约束。
Cloneable
Object提供了clone方法,实现该接口,可以进行浅克隆,不然会抛出CloneNotSupportExeption。
RandomAccess
实现该接口,实现快速随机访问的功能。
Serializable
序列化
ArrayList
看ArrayList的时候,首先它的特点就是Array,ArrayList是数组实现的。马上就应该想到的是,它内部有一个数组,但是数组的特点是固定大小,而前面看到的Array_MAX_SIZE是Integet.MAX_VALUE - 8,ArrayList不可能初始化程这个大小,那么ArrayList需要一个扩容的能力。ArrayList在扩容的时候不可能每次都扩容,所以需要一个size定义ArrayList的大小。下面是ArrayList的属性
modCount(继承自夫类)
elementData数组,ArrayList的具体实现
size,ArrayList使用该数组的大小
DEFAULT_CAPACITY默认容量
EMPTY_ELEMENTDATA
DEFAULECAPACITY_EMPTY_ELEMENTDATA
MAX_ARRAY_SIZE = Integer.MAX_SIZE - 8
DEFAULECAPACITY_EMPTY_ELEMENTDATA用于无参构造函数初始化,EMPTY_ELEMENTDATA用于有参
构造函数为0和Colletion的size0时初始化。
ArrayList继承自AbstractList,在不考虑本身特性的情况下,有size,get,add,set,remove方法需要实现。这
其中的关键就是elementData和size。我们需要在每一次结构发生改变的情况下重新定义elementData和size,有了这
两个属性,就可以实现上述5个方法了。
按步骤来看ArrayList是怎么具体实现的。
1.初始化
有3个构造函数
有参:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
在有参的情况下,将elementData定义为参数大小的Object[]数组,size为0(默认为0)
无参:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
在无参的情况下,将elementData定义为0的Object[]数组,size为0(默认为0)
Collection构造函数
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
给elementData赋值,定义size大小
三个构造函数其实就是为了初始化elementData和size
2.扩容
扩容的方法核心其实就是grow方法
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
现实将扩容值设为原来的1.5倍,如果传入值比这个值大,则改为传入值,如果这个值大于MAX_ARRAY_SIZE则
调用hugeCapacity方法。这里是唯一调用到hugeCapacity方法的地方,如果越界则抛出异常,不然就是返回
Integer.MAX_VALUE,最后copy数组。这里并没有比较minCapacity和elementData.length的大小,因为huge的调用者
是ensureExpliciteCapacity,在ensureExpliciteCapacity中判断更为合适。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
调用该方法是,结构moCount++,判断是否扩容。
ensureExplicitCapacity在内部并不是直接被调用的,而是包裹在ensureCapacityInternal中被调用,
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
该方法中重新定义传入的参数。
而ensureExplicitCapacity内部的调用者主要为4个add方法。
剩下的方法就不再介绍了,应该都能看明白了。
看了这些代码之后,对什么时候该抛异常,应该也可以加深理解。