前言

ArrayList 作为 Java 集合框架中最常用的类,在一般情况下,用它存储集合数据最适合不过。知其然知其所以然,为了能更好地认识和使用 ArrayList,本文将从下面几方面深入理解 ArrayList:

  • 为什么不用数组,用 ArrayList
  • ArrayList 特性的源码分析
  • Java 8 后 的 ArrayList
  • 正确的 ArrayList 使用姿势

为什么不用数组,用 ArrayList。

在 Java 语言中,由于普通数组受到长度限制,初始化时就需要限定数组长度,无法根据元素个数动态扩容,并且 Java 数组供开发者调用方法有限,只有取元素,获取数组长度和添加元素一些简单操作。后台在 Java 1.2 引入了强大丰富的 Collection 框架,其中用 ArrayList 来作为可动态扩容数组的列表实现来代替 Array 在日常开发的使用,ArrayList 实现所有列表的操作方法,方便开发者操作列表集合。这里我们先列举下 ArrayList 的主要特点,在后文进行一一阐述:

  • 有序存储元素

  • 允许元素重复,允许存储 null 值
  • 支持动态扩容
  • 非线程安全

为了更好地认识 ArrayList,我们首先来看下从 ArrayList 的UML类图:

从上图可以看出 ArrayList 继承了 AbstractList, 直接实现了 Cloneable, Serializable,RandomAccess 类型标志接口。

  • AbstractList 作为列表的抽象实现,将元素的增删改查都交给了具体的子类去实现,在元素的迭代遍历的操作上提供了默认实现。
  • Cloneable 接口的实现,表示了 ArrayList 支持调用 Object 的 clone 方法,实现 ArrayList 的拷贝。
  • Serializable 接口实现,说明了 ArrayList 还支持序列化和反序列操作,具有固定的 serialVersionUID 属性值。
  • RandomAccess 接口实现,表示 ArrayList 里的元素可以被高效效率的随机访问,以下标数字的方式获取元素。实现 RandomAccess 接口的列表上在遍历时可直接使用普通的for循环方式,并且执行效率上给迭代器方式更高。

ArrayList 源码分析

进入 ArrayList 源代码,从类的结构里很快就能看到 ArrayList 的两个重要成员变量:elementData 和 size

  • elementData 是一个 Object 数组,存放的元素,正是外部需要存放到 ArrayList 的元素,即 ArrayList 对象维护着这个对象数组 Object[],对外提供的增删改查以及遍历都是与这个数组有关,也因此添加到 ArrayList 的元素都是有序地存储在数组对象 elementData 中。
  • size 字段表示着当前添加到 ArrayList 的元素个数,需要注意的是它必定小于等于数组对象 elementData 的长度。一旦当 size 与 elementData 长度相同,并且还在往列表里添加元素时,ArrayList 就会执行扩容操作,用一个更长的数组对象存储先前的元素。

由于底层维护的是一个对象数组,所以向 ArrayList 集合添加的元素自然是可以重复的,允许为 null的,并且它们的索引位置各不一样。

如何扩容

了解完 ArrayList 为何有序存储元素和元素可以重复,我们再来看下作为动态数组列表,底层扩容是如何实现的。

首先,要确定下扩容的时机会是在哪里,就如上面描述 size 字段时提到的,当 size 与 elementData 长度相同,此刻再添加一个元素到集合就会出现容量不够的情况,需要进行扩容,也就是说 ArrayList 的扩容操作发生在添加方法中,并且满足一定条件时才会发生。

现在我们再来看下 ArrayList 类的代码结构,可以看到有四个添加元素的方法,分为两类:添加单个元素和添加另一个集合内的所有元素。

先从简单的方法下手分析,查看 add(E):boolean 方法实现:

public boolean add(E e) {     ensureCapacityInternal(size + 1);     elementData[size++] = e;