ArrayList的源码其实比较简单,所以我并没有跟着源码对照翻译,文本只是抽取了一些我觉得有意思或一些有疑惑的地方分析的。
一、成员变量 private static final int DEFAULT_CAPACITY = 10; // 默认容量 private static final Object[] EMPTY_ELEMENTDATA = {}; // 空实例的空数组对象 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 也是空数组对象,用于计算添加第一个元素时要膨胀多少 transient Object[] elementData; // 存储内容的数组 private int size; // 存储的数量其中elementData被声明为了transient,那么ArrayList是如何实现序列化的呢?
查看writeObject和readObject的源码如下:
可以看到在序列化的时候是把elementData里面的元素逐个取出来放到ObjectOutputStream里面的;而在反序列化的时候也是把元素逐个拿出来放回到elementData里面的;
这样繁琐的操作,其中最重要的一个好处就是节省空间,因为elementData的大小是大于ArrayList中实际元素个数的。所以没必要将elementData整个序列化。
ArrayList的构造函数主要就是要初始化elementData和size,但是其中有一个还有点意思
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; } }可以看到在Collection.toArray()之后又判断了他的Class类型是不是Object[].class,这个也注释了是一个bug,那么在什么情况下会产生这种情况呢?
// test 6260652 private static void test04() { List<String> list = Arrays.asList("111", "222", "333"); System.out.println(list.getClass()); Object[] objects = list.toArray(); System.out.println(objects.getClass()); } 打印: class java.util.Arrays$ArrayList class [Ljava.lang.String;这里可以看到objects的class居然是[Ljava.lang.String,同时这里的ArrayList是java.util.Arrays.ArrayList
// java.util.Arrays.ArrayList public static <T> List<T> asList(T... a) { return new ArrayList<>(a); } private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable { private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } ... }从以上例子可以看到,由于直接将外部数组的引用直接赋值给了List内部的数组,所以List所持有的数组类型是未知的。之前讲过数组是协变的,不支持泛型,所以只有值运行时再知道数组的具体类型,所以导致了以上的bug;难怪《Effective Java》里面讲数组的协变设计,不是那么完美。
三、常用方法由于ArrayList是基于数组的,所以他的api基本都是基于System.arraycopy()实现的;
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);可以看到这是一个native方法,并且JVM有对这个方法做特殊的优化处理,
private static void test05() { int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; System.arraycopy(s1, 3, s2, 6, 2); System.out.println(Arrays.toString(s1)); System.out.println(Arrays.toString(s2)); } 打印: [1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 4, 5, 9] private static void test06() { int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; System.arraycopy(s1, 3, s2, 6, 5); System.out.println(Arrays.toString(s1)); System.out.println(Arrays.toString(s2)); } // 抛出异常`java.lang.ArrayIndexOutOfBoundsException` private static void test07() { int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; System.arraycopy(s1, 8, s2, 5, 3); System.out.println(Arrays.toString(s1)); System.out.println(Arrays.toString(s2)); } // 抛出异常`java.lang.ArrayIndexOutOfBoundsException`从上面的测试可以了解到:
System.arraycopy()就是将源数组的元素复制到目标数组中,
如果源数组和目标数组是同一个数组,就可以实现数组内元素的移动,
源数组和目标数组的下标越界,都会抛出ArrayIndexOutOfBoundsException。