ArrayList源码分析
类图

- 实现了
RandomAccess接口,可以随机访问 - 实现了
Cloneable接口,可以克隆 - 实现了
Serializable接口,可以序列化、反序列化 - 实现了
List接口,是List的实现类之一 - 实现了
Collection接口,是Java Collections Framework成员之一 - 实现了
Iterable接口,可以使用for-each迭代
属性
1 | // 序列化版本UID |
- 为什么空实例默认数组有的时候是
EMPTY_ELEMENTDATA,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA - 为什么
elementData要被transient修饰 - 为什么
elementData没有被private修饰?难道正如注释所写的non-private to simplify nested class access
构造方法
带初始容量的构造方法
1 | /** |
- 如果
initialCapacity < 0,就创建一个新的长度是initialCapacity的数组 - 如果
initialCapacity == 0,就使用EMPTY_ELEMENTDATA - 其他情况,
initialCapacity不合法,抛出异常
无参构造方法
1 | /** |
带一个集合参数的构造方法
1 | /** |
- 使用将集合转换为数组的方法
- 为了防止
c.toArray()方法不正确的执行,导致没有返回Object[],特殊做了处理 - 如果数组大小等于
0,则使用EMPTY_ELEMENTDATA
那么问题来了,什么情况下
c.toArray()会不返回Object[]呢?
1 | public static void main(String[] args) { |
我们通过这个例子可以看出来,java.util.ArrayList.toArray()方法会返回Object[]没有问题。而java.util.Arrays的私有内部类ArrayList的toArray()方法可能不返回Object[]。
为什么会这样?
我们看ArrayList的toArray()方法源码:
1 | public Object[] toArray() { |
使用了Arrays.copyOf()方法:
1 | public static <T> T[] copyOf(T[] original, int newLength) { |
copyOf()的具体实现:
1 | public static <T,U> T[] copyOf(U[] original, |
我们知道ArrayList中elementData就是Object[]类型,所以ArrayList的toArray()方法必然会返回Object[]。
我们再看一下java.util.Arrays的内部ArrayList源码(截取的部分源码):
1 | private static class ArrayList<E> extends AbstractList<E> |
这是Arrays.asList()方法源码
1 | public static <T> List<T> asList(T... a) { |
不难看出来java.util.Arrays的内部ArrayList的toArray()方法,是构造方法接收什么类型的数组,就返回什么类型的数组。
所以,在我们上面的例子中,实际上返回的是String类型的数组,再将其中的元素赋值成Object类型的,自然报错。
我们还是继续看ArrayList吧…
插入方法
在列表最后添加指定元素
1 | /** |
- 在父类
AbstractList上,定义了modCount属性,用于记录数组修改的次数。
在指定位置添加指定元素
1 | /** |
插入方法调用的其他私有方法
1 | /** |
扩容方法
1 | /** |
- 通常情况新容量是原来容量的1.5倍
- 如果原容量的1.5倍比
minCapacity小,那么就扩容到minCapacity - 特殊情况扩容到
Integer.MAX_VALUE
看完构造方法、添加方法、扩容方法之后,上文第1个问题终于有了答案。原来,
new ArrayList()会将elementData赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,new ArrayList(0)会将elementData赋值为 EMPTY_ELEMENTDATA,EMPTY_ELEMENTDATA添加元素会扩容到容量为1,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10。
通过反射我们可以验证这一想法。如下:
1 | public static void main(String[] args) { |
移除方法
移除指定下标元素方法
1 | /** |
移除指定元素方法
1 | /** |
移除方法名字、参数的个数都一样,使用的时候要注意。
私有移除方法
1 | /* |
查找方法
查找指定元素的所在位置
1 | /** |
查找指定位置的元素
1 | /** |
该方法直接返回
elementData数组指定下标的元素,效率还是很高的。所以ArrayList,for循环遍历效率也是很高的。
序列化方法
1 | /** |
反序列化方法
1 | /** |
看完序列化,反序列化方法,我们终于又能回答开篇的第二个问题了。
elementData之所以用transient修饰,是因为JDK不想将整个elementData都序列化或者反序列化,而只是将size和实际存储的元素序列化或反序列化,从而节省空间和时间。
创建子数组
1 | public List<E> subList(int fromIndex, int toIndex) { |
我们看一下简短版的SubList:
1 | private class SubList extends AbstractList<E> implements RandomAccess { |
- SubList的set()方法,是直接修改ArrayList中
elementData数组的,使用中应该注意 - SubList是没有实现
Serializable接口的,是不能序列化的
迭代器
创建迭代器方法
1 | public Iterator<E> iterator() { |
Itr属性
1 | // 下一个要返回的元素的下标 |
Itr的hasNext() 方法
1 | public boolean hasNext() { |
Itr的next()方法
1 | public E next() { |
在迭代的时候,会校验
modCount是否等于expectedModCount,不等于就会抛出著名的ConcurrentModificationException异常。什么时候会抛出ConcurrentModificationException?
1 | public static void main(String[] args) { |
那怎么写才能不抛出
ConcurrentModificationException?很简单,将list.remove(number);换成iterator.remove();即可。why?请看Itr的remove()源码…
Itr的remove()方法
1 | public void remove() { |
原因就是因为Itr的remove()方法,移除之后将modCount重新赋值给 expectedModCount。这就是源码,不管单线程还是多线程,只要违反了规则,就会抛异常。
源码看的差不多了,开篇的问题却还剩一个!到底为什么
elementData没有用private修饰呢?
我们知道的,private修饰的变量,内部类也是可以访问到的。难道注释中non-private to simplify nested class access的这句话有毛病?
当我们看表面看不到什么东西的时候,不妨看一下底层。
测试类代码:

一顿javac、javap之后(使用JDK8):

再一顿javac、javap之后(使用JDK11):

虽然字节码指令我还看不太懂,但是我能品出来,注释是没毛病的,private修饰的确会影响内部类的访问。
ArrayList类注释翻译
类注释还是要看的,能给我们一个整体的了解这个类。我将ArrayList的类注释大概翻译整理了一下:
- ArrayList是实现
List接口的可自动扩容的数组。实现了所有的List操作,允许所有的元素,包括null值。 - ArrayList大致和Vector相同,除了ArrayList是非同步的。
sizeisEmptygetsetiterator和listIterator方法时间复杂度是O(1),常量时间。其他方法是O(n),线性时间。- 每一个ArrayList实例都有一个
capacity(容量)。capacity是用于存储列表中元素的数组的大小。capacity至少和列表的大小一样大。 - 如果多个线程同时访问ArrayList的实例,并且至少一个线程会修改,必须在外部保证ArrayList的同步。修改包括添加删除扩容等操作,仅仅设置值不包括。这种场景可以用其他的一些封装好的同步的
list。如果不存在这样的Object,ArrayList应该用Collections.synchronizedList包装起来最好在创建的时候就包装起来,来保证同步访问。 iterator()和listIterator(int)方法是fail-fast的,如果在迭代器创建之后,列表进行结构化修改,迭代器会抛出ConcurrentModificationException。- 面对并发修改,迭代器快速失败、清理,而不是在未知的时间不确定的情况下冒险。请注意,快速失败行为不能被保证。通常来讲,不能同步进行的并发修改几乎不可能做任何保证。因此,写依赖这个异常的程序的代码是错误的,快速失败行为应该仅仅用于防止
bug。
总结
- ArrayList底层的数据结构是数组
- ArrayList可以自动扩容,不传初始容量或者初始容量是
0,都会初始化一个空数组,但是如果添加元素,会自动进行扩容,所以,创建ArrayList的时候,给初始容量是必要的 Arrays.asList()方法返回的是的Arrays内部的ArrayList,用的时候需要注意subList()返回内部类,不能序列化,和ArrayList共用同一个数组- 迭代删除要用,迭代器的
remove方法,或者可以用倒序的for循环 - ArrayList重写了序列化、反序列化方法,避免序列化、反序列化全部数组,浪费时间和空间
elementData不使用private修饰,可以简化内部类的访问