1. 概述

2. 快速上手

ArrayList支持泛型,创建 ArrayList 的时候可以指定元素的类型:

  1. ArrayList<String> names = new ArrayList<String>();
  2. ArrayList<Student> students = new ArrayList<Student>();

ArrayList常用方法:

  1. // JDK 10.0.2
  2. // 追加元素
  3. void add(E e);
  4. // 删除元素
  5. E remove();
  6. // 是否包含元素
  7. boolean contains(Object o);
  8. // 设置指定索引的值
  9. E set(int index,E element);
  10. // 清空列表
  11. void clear();
  12. // 从前往后查找指定元素的索引,基于equals
  13. int indexOf(Object o);
  14. // 从后往前查找指定元素的索引
  15. int lastIndexOf(Object o);
  16. // 返回列表元素的数量
  17. int size();
  18. // 判断元素是否为空,size == 0
  19. int isEmpty();
  20. // 返回此对象的迭代器
  21. Interator<E> iterator();
  22. // 将ArrayList转成数组,返回的是Object的数组
  23. Object[] toArray();
  24. // 将ArrayList转换成指定类型的数组
  25. <T> T[] toArray(T[] a);
  26. // 将元素添加到指定的索引
  27. void add(int index,E element);
  28. // 删除指定索引的元素
  29. E remove(int index);
  30. // 截取从 fromIndex(包含) 到 toIndex(不包含) 范围的元素列表
  31. List<E> subList(int fromIndex, int toIndex);

上面的代码中演示了 ArrayList 比较常用的 API,接下来我们可以看一个示例:

  1. ArrayList<String> names = new ArrayList<String>();
  2. names.add("James");
  3. names.add("Shaw");
  4. names.add(1,"Rod");
  5. for(int i = 0; i < names.size(); i++) {
  6. System.out.println(names.get(i));
  7. }
  8. names.contains("shaw");
  9. names.isEmpty();
  10. names.size();
  11. Object[] obj = names.toArray();
  12. String[] strArr = name.toArray(new String());
  13. List<String> nameList = names.subList(1,names.size())

3. 基本原理

3.1 属性

  1. Object[] elementData
  2. private int size;
  3. protected transient int modCount = 0;

ArrayList内部有一个elementData,作为数据的容器,接下来就是size属性它记录了当前实际保存数据的数量,最后就是modCount它记录了 elementData 内部结构修改的次数(增加,修改,删除)等。

3.2 add方法

我们可以看下addremove方法的主要实现:

  1. public boolean add(E e) {
  2. modCount++;
  3. add(e, elementData, size);
  4. return true;
  5. }
  1. add方法首先对modCount一次自增操作

  2. 接着add方法调用了重载的add(E e, Object[] elementData, int s)方法,传入了 需要添加的元素保存元素的数组以及 当前保存的元素的数量

  3. 最后返回true

我们可以追踪一下 add(E e, Object[] elementData, int s)

  1. private void add(E e, Object[] elementData, int s) {
  2. if (s == elementData.length)
  3. elementData = grow();
  4. elementData[s] = e;
  5. size = s + 1;
  6. }

它首先判断了传入的参数size和保存元素的数组elementDatalength属性是否相等,如果不相等它会直接将需要添加的元素放到elementDatasize位置,然后对size做一次自增操作;如果相等会调用grow()方法,将扩容后的数组赋值给elementData,我们可以来看一下grow()方法的实现:

  1. private Object[] grow() {
  2. return grow(size + 1);
  3. }

这个方法很简单,它调用了grow(int minCapacity)方法返回了扩容后的数组:

  1. private Object[] grow(int minCapacity) {
  2. return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
  3. }

它首先调用了Arrays.copyOf方法,然后将elementData以及newCapacity(int minCapacity)方法返回的参数作为参数传递给了Arrays.copyOf,我们可以来看一下newCapacity(int minCapacity)方法的代码:

  1. private int newCapacity(int minCapacity) {
  2. // overflow-conscious code
  3. int oldCapacity = elementData.length;
  4. // 1.5 倍扩容
  5. int newCapacity = oldCapacity + (oldCapacity >> 1);
  6. // 如果扩容后容量小于参数需要的最小容量
  7. if (newCapacity - minCapacity <= 0) {
  8. // 如果是第一次添加
  9. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
  10. // 返回 Math.max(DEFAULT_CAPACITY,minCapacity)
  11. return Math.max(DEFAULT_CAPACITY, minCapacity);
  12. if (minCapacity < 0) // overflow
  13. throw new OutOfMemoryError();
  14. // 返回 newCaiacity
  15. return minCapacity;
  16. }
  17. // 否则判断是否超过了限制,如果没有的话返回 newCapacity
  18. return (newCapacity - MAX_ARRAY_SIZE <= 0)
  19. ? newCapacity
  20. : hugeCapacity(minCapacity);
  21. }

3.3 remove

我们接下来看remove方法:

  1. public E remove(int index) {
  2. // 索引检查
  3. Objects.checkIndex(index, size);
  4. // 取出所有元素
  5. final Object[] es = elementData;
  6. // 获取即将删除的元素
  7. E oldValue = (E) es[index];
  8. // 删除方法
  9. fastRemove(es, index);
  10. // 返回
  11. return oldValue;
  12. }

它首先对参数index进行了一个校验由于方法实现比较简单,所以不再过多阐述,我们主要来看一下fastRemove(Object[] es, int i)方法实现:

  1. private void fastRemove(Object[] es, int i) {
  2. modCount++;
  3. final int newSize;
  4. // 如果size - 1 > i(判断是不是最后一个元素),进行移为删除
  5. if ((newSize = size - 1) > i)
  6. System.arraycopy(es, i + 1, es, i, newSize - i);
  7. // 末尾删除,直接赋值为null
  8. es[size = newSize] = null;
  9. }

它首先对modCount进行了自增操作,紧接着判断删除的是不是末尾删除,如果不是的会话会调用System.arrayCopy(Object src, int srcPos,Object dest, int destPos,int length)对元素进行移位。es[size=newSize]=null这行代码对size进行减1,然后将最后一个元素赋值为null。设置为null之后不再引用原对象,如果对象不再被其他对象引用,那么就可以被垃圾回收。

4. 迭代

刚才我们大概的讲述了一下ArrayList的常用 API以及基本原理,接下来我们来看一下 ArrayList 的迭代。我们来一个简单的例子,循环 ArrayList 中的每一个元素,ArrayList支持foreach语法:

  1. ArrayList<String> names = new ArrayList<String>();
  2. names.add("Shaw");
  3. names.add("James");
  4. names.add("Rod");
  5. for(String name : names) {
  6. System.out.println(name);
  7. }

当然,ArrayList也支持通过索引的方式访问:

  1. for(int i = 0; i < names.size(); i++) {
  2. System.out.println(names.get(i));
  3. }

看上去foreach的语法更加的简洁,而且也适用与其他容器,更加的通用。这种foreach语法的实现是什么样子的呢?其实,编译之后,它会转换成类似这样的代码:

  1. Iterator<String> iterator = names.iterator();
  2. while(iterator.hasNext()) {
  3. System.out.println(iterator.next());
  4. }

4.1 Iterable

要了解foreach背后的原理之前,我们需要先了解Iterable接口,ArrayList实现了Iterable接口,它的字面意思就是表示可迭代,它的定义如下:

  1. public interface Iterable<T> {
  2. Iterator<T> iterator();
  3. }

它的定义很简单,就是要求实现iterator方法,返回一个Iterator迭代器接口对象,Iterator接口定义如下:

  1. public interface Iterator<E> {
  2. boolean hasNext();
  3. E next();
  4. void remove();
  5. }
  1. hasNext判断是否是否还有元素可以访问
  2. next返回迭代的下一个元素
  3. remove删除最后返回的元素

只要对象实现了iterator接口就可以使用foreach语法,编译器会自动调用iteratoriterable接口的方法。可能对iterableiterator这两个接口有点绕,我们可以来看下它们的关系:

  1. Iterable表示对象可迭代,iterator方法要求返回一个Iterator对象,实际通过Iterator接口的方法进行遍历
  2. 如果对象实现了Iterable接口,就可以使用foreach语法
  3. 类可以不实现Iterable接口,也可以创建Iterator对象

4.2 ListIterable

除了iteratorArrayList还实现了List接口的listIterator方法:

  1. ListIterator<E> listIterator();
  2. ListIterator<E> listIterator(int index);

ListIterator接口继承了Iterator接口,增加了一些方法,如向前遍历、添加元素、修改元素、返回索引位置等,方法定义如下:

  1. void add(E e);
  2. int nextIndex();
  3. E previous();
  4. int previousIndex();
  5. void set(E e);

listIterator方法返回的迭代器从0开始,而listIterator(int index)返回的迭代器从指定索引index开始。比如,从末尾向前遍历,代码为:

  1. ListIterator<String> listIt = names.listIterator(names.size());
  2. while(listIt.hashPrevios) {
  3. System.out.println(listIt.prevsious());
  4. }

4.3 迭代器的坑

关于迭代器有一种常见的失误操作,就是在迭代的中间调用容器的删除方法,比如,要删除 ArrayList 中所有小于100的数,直觉上,代码可以这样写:

  1. public void remove(ArrayList<Integer> list) {
  2. for(Integer element : list) {
  3. if(element < 100){
  4. element.remove();
  5. }
  6. }
  7. }

但是在运行时会抛出异常:java.util.ConcurrentModificationException发生了并发修改异常,这是为什么呢?因为迭代器内部会维护一些索引位置相关的数据,要求在迭代的过程中,容器不能发生结构性变化,否则索引位置就失效了。所谓结构性变化就是添加、删除、插入元素,只是修改元素不会发生结构性变化。

如何避免发生异常呢?可以使用迭代器的remove方法,如下所示:

  1. public void remove(ArrayList<Integer> list) {
  2. Iterator<Integer> iterator = list.iterator();
  3. while(iterator.hasNext()) {
  4. if(iterator.next() < 100) {
  5. iterator.remove();
  6. }
  7. }
  8. }

迭代器是如何知道发生了结构性变化,并抛出异常?它自己的remove方法为何又可以使用?我们可以简单来了解一下迭代器的原理。

4.4 迭代实现的原理

我们可以来看一下ArrayListiterator方法的实现,代码为:

  1. public Iterator<E> iterator() {
  2. return new Itr();
  3. }

它返回了内部类Itr的对象,Itr实现了Iterator接口,声明为:

  1. private class Itr implements Iterator<E> {}

它有三个实例变量,为:

  1. // 下一个要返回的元素的索引
  2. int cursor; // index of next element to return
  3. // 返回的最后一个元素的索引,如果没有,则为-1
  4. int lastRet = -1; // index of last element returned; -1 if no such
  5. int expectedModCount = modCount;

cursor表示下一个要返回的位置,lastRet表示最后一个返回的索引位置,expectedModCount表示期望修改的次数,初始化为外部类当前修改次数modCount,回顾一下;成员内部类可以访问外部类的实例变量,每次发生结构性变化的时候modCount都会自增,而每次迭代器操作的时候都会检查expectedModCount是否与modCount相等,这样就能检测出结构性变化。

我们可以具体的来一下,它是如何实现Iterator接口中的每个方法的,先看hasNext(),代码为:

  1. public boolean hasNext() {
  2. return cursor != size;
  3. }

代码很简单,当前cursor指向元素的索引不等于size则表示还有下一个元素。我们再来看next方法:

  1. public E next() {
  2. checkForComodification();
  3. int i = cursor;
  4. if (i >= size)
  5. throw new NoSuchElementException();
  6. Object[] elementData = ArrayList.this.elementData;
  7. if (i >= elementData.length)
  8. throw new ConcurrentModificationException();
  9. cursor = i + 1;
  10. return (E) elementData[lastRet = i];
  11. }

它首先调用了checkForComodification()方法,检查ArrayList是否发生了结构性变化,如果发生了结构性变化则抛出ConcurretModificationException,方法定义如下:

  1. final void checkForComodification() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. }

如果没有发生变化,就更新cursorlastRet的值使其保持语义,然后返回对应的元素。最后我们来看一下remove方法的实现:

  1. public void remove() {
  2. if (lastRet < 0)
  3. throw new IllegalStateException();
  4. checkForComodification();
  5. try {
  6. ArrayList.this.remove(lastRet);
  7. cursor = lastRet;
  8. lastRet = -1;
  9. expectedModCount = modCount;
  10. } catch (IndexOutOfBoundsException ex) {
  11. throw new ConcurrentModificationException();
  12. }
  13. }

它调用了ArrayListremove方法,有同时更新了cursorlastRetexpectedModCount的值,所以它可以正确的删除。不过,需要注意的是,调用remove方法前必须先调用next,比如,通过迭代器删除所有元素,直觉上,可以这么写:

  1. public static void removeAll(ArrayList<Integer> list) {
  2. Iterator<Integer> it = list.iterator();
  3. while(it.hasNext()) {
  4. it.remove();
  5. }
  6. }

实际运行,会抛出java.lang.IllegalStateException,正确写法是:

  1. public static void removeAll(ArrayList<Integer> list) {
  2. Iterator<Integer> it = list.iterator();
  3. while(it.hasNext()) {
  4. it.next();
  5. it.remove();
  6. }
  7. }

调用next()方法是为了更新内部属性,保持语义,否则的话lastRet的值就是-1就会抛出java.lang.IllegalStateException异常。

当然咯,要删除所有元素,ArrayList有现成的方法clear()

listIterator的实现使用了另一个内部类ListItr,它继承自Itr,基本思路类似,不在阐述。

4.5 迭代器的好处

  1. 通用,适用于各种容器类,提供一致性的方式访问
  2. 关注点分离,不需要关注数据的组织方式,将数据的实际组织方式和数据的迭代编译方式相分离,是一种常见的设计模式
  3. 从封装的角度来说,迭代器封装了数组组织方式的迭代操作,提供了简单和一致的接口

5. Array List实现的接口

Java的各种容器类都有一个共性操作,这些共性以接口的方式体现,刚才介绍的Iterator接口就是,此外,ArrayList还实现了主要三个接口:CollectionListRandomAccess,我们逐个介绍。

5.1 Collection

Collection表示一个数据集合,数据间没有位置或顺序的概念,定义为:

  1. public interface Collection<E> extends Iterable<E> {
  2. // 返回这个集合的元素数量
  3. int size();
  4. // 这个集合是否包含元素
  5. boolean isEmpty();
  6. // true: 这个集合包含指定的元素,false 反之
  7. boolean contains(Object o);
  8. // 返回此集合中元素的迭代器
  9. Iterator<E> iterator();
  10. // 返回包含此集合所有元素的数组
  11. Object[] toArray();
  12. // 返回包含此集合中所有元素指定类型的数组
  13. <T> T[] toArray(T[] a);
  14. // 添加到末尾
  15. boolean add(E e);
  16. // 删除指定元素
  17. boolean remove(Object o);
  18. // 是否包含指定元素
  19. boolean containsAll(Collection<?> c);
  20. // 添加集合
  21. boolean addAll(Collection<? extends E> c);
  22. // 按集合删除
  23. boolean removeAll(Collection<?> c);
  24. // 将删除的条件对外提供
  25. default boolean removeIf(Predicate<? super E> filter) {
  26. Objects.requireNonNull(filter);
  27. boolean removed = false;
  28. final Iterator<E> each = iterator();
  29. while (each.hasNext()) {
  30. if (filter.test(each.next())) {
  31. each.remove();
  32. removed = true;
  33. }
  34. }
  35. return removed;
  36. }
  37. // 包含指定元素
  38. boolean retainAll(Collection<?> c);
  39. // 清空集合
  40. void clear();
  41. boolean equals(Object o);
  42. int hashCode();
  43. // 分隔
  44. default Spliterator<E> spliterator() {
  45. return Spliterators.spliterator(this, 0);
  46. }
  47. // Stream流
  48. default Stream<E> stream() {
  49. return StreamSupport.stream(spliterator(), false);
  50. }
  51. default Stream<E> parallelStream() {
  52. return StreamSupport.stream(spliterator(), true);
  53. }
  54. }

retainAll表示只保留参数容器中的元素,其他元素则会删除。Java 8对Collection添加了几个默认方法,包括removeIfstreamsplierator等。

抽象类AbstractCollection对一些方法提供了默认实现,实现的方式是通过迭代器方法逐个操作。我们可以来看一下这几个方法:

  1. public boolean isEmpty() {
  2. return size() == 0;
  3. }
  4. public boolean contains(Object o) {
  5. Iterator<E> it = iterator();
  6. if (o==null) {
  7. while (it.hasNext())
  8. if (it.next()==null)
  9. return true;
  10. } else {
  11. while (it.hasNext())
  12. if (o.equals(it.next()))
  13. return true;
  14. }
  15. return false;
  16. }
  17. public Object[] toArray() {
  18. // Estimate size of array; be prepared to see more or fewer elements
  19. Object[] r = new Object[size()];
  20. Iterator<E> it = iterator();
  21. for (int i = 0; i < r.length; i++) {
  22. if (! it.hasNext()) // fewer elements than expected
  23. return Arrays.copyOf(r, i);
  24. r[i] = it.next();
  25. }
  26. return it.hasNext() ? finishToArray(r, it) : r;
  27. }
  28. public boolean remove(Object o) {
  29. Iterator<E> it = iterator();
  30. if (o==null) {
  31. while (it.hasNext()) {
  32. if (it.next()==null) {
  33. it.remove();
  34. return true;
  35. }
  36. }
  37. } else {
  38. while (it.hasNext()) {
  39. if (o.equals(it.next())) {
  40. it.remove();
  41. return true;
  42. }
  43. }
  44. }
  45. return false;
  46. }
  47. public boolean containsAll(Collection<?> c) {
  48. for (Object e : c)
  49. if (!contains(e))
  50. return false;
  51. return true;
  52. }
  53. public boolean addAll(Collection<? extends E> c) {
  54. boolean modified = false;
  55. for (E e : c)
  56. if (add(e))
  57. modified = true;
  58. return modified;
  59. }
  60. public boolean removeAll(Collection<?> c) {
  61. Objects.requireNonNull(c);
  62. boolean modified = false;
  63. Iterator<?> it = iterator();
  64. while (it.hasNext()) {
  65. if (c.contains(it.next())) {
  66. it.remove();
  67. modified = true;
  68. }
  69. }
  70. return modified;
  71. }
  72. public boolean retainAll(Collection<?> c) {
  73. Objects.requireNonNull(c);
  74. boolean modified = false;
  75. Iterator<E> it = iterator();
  76. while (it.hasNext()) {
  77. if (!c.contains(it.next())) {
  78. it.remove();
  79. modified = true;
  80. }
  81. }
  82. return modified;
  83. }

5.2 List

List表示有序可重复支持按索引访问的集合,它继承了Collection,增加了以下方法:

  1. boolean addAll(int index, Collection<? extends E> c);
  2. default void replaceAll(UnaryOperator<E> operator) {
  3. Objects.requireNonNull(operator);
  4. final ListIterator<E> li = this.listIterator();
  5. while (li.hasNext()) {
  6. li.set(operator.apply(li.next()));
  7. }
  8. }
  9. default void sort(Comparator<? super E> c) {
  10. Object[] a = this.toArray();
  11. Arrays.sort(a, (Comparator) c);
  12. ListIterator<E> i = this.listIterator();
  13. for (Object e : a) {
  14. i.next();
  15. i.set((E) e);
  16. }
  17. }
  18. E get(int index);
  19. E set(int index, E element);
  20. void add(int index, E element);
  21. E remove(int index);
  22. int indexOf(Object o);
  23. int lastIndexOf(Object o);
  24. ListIterator<E> listIterator();
  25. ListIterator<E> listIterator(int index);
  26. List<E> subList(int fromIndex, int toIndex);
  27. static <E> List<E> of() {
  28. return ImmutableCollections.List0.instance();
  29. }
  30. static <E> List<E> of(E e1) {
  31. return new ImmutableCollections.List1<>(e1);
  32. }
  33. static <E> List<E> of(E e1, E e2) {
  34. return new ImmutableCollections.List2<>(e1, e2);
  35. }
  36. static <E> List<E> of(E... elements) {
  37. switch (elements.length) { // implicit null check of elements
  38. case 0:
  39. return ImmutableCollections.List0.instance();
  40. case 1:
  41. return new ImmutableCollections.List1<>(elements[0]);
  42. case 2:
  43. return new ImmutableCollections.List2<>(elements[0], elements[1]);
  44. default:
  45. return new ImmutableCollections.ListN<>(elements);
  46. }
  47. }
  48. static <E> List<E> copyOf(Collection<? extends E> coll) {
  49. if (coll instanceof ImmutableCollections.AbstractImmutableList) {
  50. return (List<E>)coll;
  51. } else {
  52. return (List<E>)List.of(coll.toArray());
  53. }
  54. }

这些方法都与位置相关,容易理解,不做阐述。Java 8对List接口增加了几个默认方法,包括sortreplaceAllspliterator;Java 9增加了多个重载的of方法,可以根据一个或多个元素生成一个不变的List,具体不介绍,可以看API文档。

5.3 Random Access

RamdomAccess的定义为:

  1. public interface RandomAccess {
  2. }

没有定义任何方法,这是为什么呢?因为RandomAccess是一个Marker interface标记接口,用于代表类的一种属性或者说类具备某种功能。

例如,在一些底层实现中会去判断接口有没有实现RandomAccess,如果实现了RandomAccess会使用索引进行查找,反之使用迭代器。比如Collections类中的binarySearch方法。

6. 相关方法

6.1 Arrays.asList

Arrays.asList用于将一个数组转换为一个List集合,需要注意的是的返回的这个List集合并不是ArrayList对象,而是Arrays内部的ArrayList对象,它表示一个不允许发生结构性变化的ArrayList对象,比如对这个返回的对象调用add方法,会发生java.lang.UnsupportedOperationException异常,代码示例如下:

  1. String[] names = new String[]{"zs", "ls", "ww"};
  2. ArrayList<String> nameList = Arrays.asList(names);
  3. nameList.add("zl");

Arrays内部的ArrayList继承了AbstractList,它表示一个不可修改的列表,如果想要数组转换后的ArrayList是可变的或者说是可修改的,可以这么做:

  1. String[] names = new String[]{"zs", "ls", "ww"};
  2. ArrayList<String> nameList = new ArrayList<String>(Arrays.asList(names));
  3. nameList.add("zl");

在这个内部类中,内部的数组使用的就是传入的数组,没有拷贝,也没有动态改变大小,对数组的修改也会反应到List当中。

6.2 ArrayList.toArray

Arraylist.toArray()可以将ArrarList对象转换成Object数组:

  1. ArrayList<String> names = new ArrayList<String>();
  2. names.add("zs");
  3. names.add("ls");
  4. names.add("ww");
  5. Object[] objs = new Object[namesList.size()];
  6. objs = nameList.toArray();

如果希望将ArrayList对象转换后的数组是初始化ArrayList时候指定的泛型类型,可以这么做:

  1. ArrayList<String> names = new ArrayList<String>();
  2. names.add("zs");
  3. names.add("ls");
  4. names.add("ww");
  5. String[] objs = new String[namesList.size()];
  6. objs = nameList.toArray(new String[0]);

7. 特点分析

  1. 支持随机访问,如果按照索引查找内容,速度是O(1),一步到位
  2. 不是一个线程安全的集合,考虑线程安全可以使用VectorCopyOnWriteArrayListVectorArrayList实现类似,使用synchronized实现了线程安全