1. 概述
2. 快速上手
ArrayList
支持泛型,创建 ArrayList 的时候可以指定元素的类型:
ArrayList names = new ArrayList();
ArrayList students = new ArrayList();
ArrayList常用方法:
// JDK 10.0.2
// 追加元素
void add(E e);
// 删除元素
E remove();
// 是否包含元素
boolean contains(Object o);
// 设置指定索引的值
E set(int index,E element);
// 清空列表
void clear();
// 从前往后查找指定元素的索引,基于equals
int indexOf(Object o);
// 从后往前查找指定元素的索引
int lastIndexOf(Object o);
// 返回列表元素的数量
int size();
// 判断元素是否为空,size == 0
int isEmpty();
// 返回此对象的迭代器
Interator iterator();
// 将ArrayList转成数组,返回的是Object的数组
Object[] toArray();
// 将ArrayList转换成指定类型的数组
T[] toArray(T[] a);
// 将元素添加到指定的索引
void add(int index,E element);
// 删除指定索引的元素
E remove(int index);
// 截取从 fromIndex(包含) 到 toIndex(不包含) 范围的元素列表
List subList(int fromIndex, int toIndex);
上面的代码中演示了 ArrayList 比较常用的 API,接下来我们可以看一个示例:
ArrayList names = new ArrayList();
names.add("James");
names.add("Shaw");
names.add(1,"Rod");
for(int i = 0; i < names.size(); i++) {
System.out.println(names.get(i));
}
names.contains("shaw");
names.isEmpty();
names.size();
Object[] obj = names.toArray();
String[] strArr = name.toArray(new String());
List nameList = names.subList(1,names.size())
3. 基本原理
3.1 属性
Object[] elementData
private int size;
protected transient int modCount = 0;
ArrayList内部有一个elementData
,作为数据的容器,接下来就是size
属性它记录了当前实际保存数据的数量,最后就是modCount
它记录了 elementData 内部结构修改的次数(增加,修改,删除)等。
3.2 add方法
我们可以看下add
和remove
方法的主要实现:
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
add
方法首先对modCount
一次自增操作- 接着
add
方法调用了重载的add(E e, Object[] elementData, int s)
方法,传入了 需要添加的元素、保存元素的数组以及 当前保存的元素的数量 - 最后返回
true
我们可以追踪一下 add(E e, Object[] elementData, int s) :
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
它首先判断了传入的参数size
和保存元素的数组elementData
的length
属性是否相等,如果不相等它会直接将需要添加的元素放到elementData
的size
位置,然后对size
做一次自增操作;如果相等会调用grow()
方法,将扩容后的数组赋值给elementData
,我们可以来看一下grow()
方法的实现:
private Object[] grow() {
return grow(size + 1);
}
这个方法很简单,它调用了grow(int minCapacity)
方法返回了扩容后的数组:
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
}
它首先调用了Arrays.copyOf
方法,然后将elementData
以及newCapacity(int minCapacity)
方法返回的参数作为参数传递给了Arrays.copyOf
,我们可以来看一下newCapacity(int minCapacity)
方法的代码:
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 1.5 倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容后容量小于参数需要的最小容量
if (newCapacity - minCapacity <= 0) {
// 如果是第一次添加
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// 返回 Math.max(DEFAULT_CAPACITY,minCapacity)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 返回 newCaiacity
return minCapacity;
}
// 否则判断是否超过了限制,如果没有的话返回 newCapacity
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
3.3 remove
我们接下来看remove
方法:
public E remove(int index) {
// 索引检查
Objects.checkIndex(index, size);
// 取出所有元素
final Object[] es = elementData;
// 获取即将删除的元素
E oldValue = (E) es[index];
// 删除方法
fastRemove(es, index);
// 返回
return oldValue;
}
它首先对参数index
进行了一个校验由于方法实现比较简单,所以不再过多阐述,我们主要来看一下fastRemove(Object[] es, int i)
方法实现:
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
// 如果size - 1 > i(判断是不是最后一个元素),进行移为删除
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
// 末尾删除,直接赋值为null
es[size = newSize] = null;
}
它首先对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
语法:
ArrayList names = new ArrayList();
names.add("Shaw");
names.add("James");
names.add("Rod");
for(String name : names) {
System.out.println(name);
}
当然,ArrayList也支持通过索引的方式访问:
for(int i = 0; i < names.size(); i++) {
System.out.println(names.get(i));
}
看上去foreach
的语法更加的简洁,而且也适用与其他容器,更加的通用。这种foreach
语法的实现是什么样子的呢?其实,编译之后,它会转换成类似这样的代码:
Iterator iterator = names.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
4.1 Iterable
要了解foreach
背后的原理之前,我们需要先了解Iterable
接口,ArrayList
实现了Iterable
接口,它的字面意思就是表示可迭代,它的定义如下:
public interface Iterable {
Iterator iterator();
}
它的定义很简单,就是要求实现iterator
方法,返回一个Iterator
迭代器接口对象,Iterator
接口定义如下:
public interface Iterator {
boolean hasNext();
E next();
void remove();
}
hasNext
判断是否是否还有元素可以访问next
返回迭代的下一个元素remove
删除最后返回的元素
只要对象实现了iterator
接口就可以使用foreach
语法,编译器会自动调用iterator
和iterable
接口的方法。可能对iterable
和iterator
这两个接口有点绕,我们可以来看下它们的关系:
Iterable
表示对象可迭代,iterator
方法要求返回一个Iterator
对象,实际通过Iterator
接口的方法进行遍历- 如果对象实现了
Iterable
接口,就可以使用foreach
语法 - 类可以不实现
Iterable
接口,也可以创建Iterator
对象
4.2 ListIterable
除了iterator
,ArrayList
还实现了List
接口的listIterator
方法:
ListIterator listIterator();
ListIterator listIterator(int index);
ListIterator
接口继承了Iterator
接口,增加了一些方法,如向前遍历、添加元素、修改元素、返回索引位置等,方法定义如下:
void add(E e);
int nextIndex();
E previous();
int previousIndex();
void set(E e);
listIterator
方法返回的迭代器从0
开始,而listIterator(int index)
返回的迭代器从指定索引index
开始。比如,从末尾向前遍历,代码为:
ListIterator listIt = names.listIterator(names.size());
while(listIt.hashPrevios) {
System.out.println(listIt.prevsious());
}
4.3 迭代器的坑
关于迭代器有一种常见的失误操作,就是在迭代的中间调用容器的删除方法,比如,要删除 ArrayList 中所有小于100的数,直觉上,代码可以这样写:
public void remove(ArrayList list) {
for(Integer element : list) {
if(element < 100){
element.remove();
}
}
}
但是在运行时会抛出异常:java.util.ConcurrentModificationException
发生了并发修改异常,这是为什么呢?因为迭代器内部会维护一些索引位置相关的数据,要求在迭代的过程中,容器不能发生结构性变化,否则索引位置就失效了。所谓结构性变化就是添加、删除、插入元素,只是修改元素不会发生结构性变化。
如何避免发生异常呢?可以使用迭代器的remove
方法,如下所示:
public void remove(ArrayList list) {
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
if(iterator.next() < 100) {
iterator.remove();
}
}
}
迭代器是如何知道发生了结构性变化,并抛出异常?它自己的remove
方法为何又可以使用?我们可以简单来了解一下迭代器的原理。
4.4 迭代实现的原理
我们可以来看一下ArrayList
中iterator
方法的实现,代码为:
public Iterator iterator() {
return new Itr();
}
它返回了内部类Itr
的对象,Itr
实现了Iterator
接口,声明为:
private class Itr implements Iterator {}
它有三个实例变量,为:
// 下一个要返回的元素的索引
int cursor; // index of next element to return
// 返回的最后一个元素的索引,如果没有,则为-1
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
cursor
表示下一个要返回的位置,lastRet
表示最后一个返回的索引位置,expectedModCount
表示期望修改的次数,初始化为外部类当前修改次数modCount
,回顾一下;成员内部类可以访问外部类的实例变量,每次发生结构性变化的时候modCount
都会自增,而每次迭代器操作的时候都会检查expectedModCount
是否与modCount
相等,这样就能检测出结构性变化。
我们可以具体的来一下,它是如何实现Iterator
接口中的每个方法的,先看hasNext()
,代码为:
public boolean hasNext() {
return cursor != size;
}
代码很简单,当前cursor
指向元素的索引不等于size
则表示还有下一个元素。我们再来看next
方法:
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
它首先调用了checkForComodification()
方法,检查ArrayList
是否发生了结构性变化,如果发生了结构性变化则抛出ConcurretModificationException
,方法定义如下:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
如果没有发生变化,就更新cursor
和lastRet
的值使其保持语义,然后返回对应的元素。最后我们来看一下remove
方法的实现:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
它调用了ArrayList
的remove
方法,有同时更新了cursor
、lastRet
、expectedModCount
的值,所以它可以正确的删除。不过,需要注意的是,调用remove
方法前必须先调用next
,比如,通过迭代器删除所有元素,直觉上,可以这么写:
public static void removeAll(ArrayList list) {
Iterator it = list.iterator();
while(it.hasNext()) {
it.remove();
}
}
实际运行,会抛出java.lang.IllegalStateException
,正确写法是:
public static void removeAll(ArrayList list) {
Iterator it = list.iterator();
while(it.hasNext()) {
it.next();
it.remove();
}
}
调用next()
方法是为了更新内部属性,保持语义,否则的话lastRet
的值就是-1
就会抛出java.lang.IllegalStateException
异常。
当然咯,要删除所有元素,ArrayList
有现成的方法clear()
。
listIterator
的实现使用了另一个内部类ListItr
,它继承自Itr
,基本思路类似,不在阐述。
4.5 迭代器的好处
- 通用,适用于各种容器类,提供一致性的方式访问
- 关注点分离,不需要关注数据的组织方式,将数据的实际组织方式和数据的迭代编译方式相分离,是一种常见的设计模式
- 从封装的角度来说,迭代器封装了数组组织方式的迭代操作,提供了简单和一致的接口
5. Array List实现的接口
Java的各种容器类都有一个共性操作,这些共性以接口的方式体现,刚才介绍的Iterator
接口就是,此外,ArrayList
还实现了主要三个接口:Collection
、List
、RandomAccess
,我们逐个介绍。
5.1 Collection
Collection
表示一个数据集合,数据间没有位置或顺序的概念,定义为:
public interface Collection extends Iterable {
// 返回这个集合的元素数量
int size();
// 这个集合是否包含元素
boolean isEmpty();
// true: 这个集合包含指定的元素,false 反之
boolean contains(Object o);
// 返回此集合中元素的迭代器
Iterator iterator();
// 返回包含此集合所有元素的数组
Object[] toArray();
// 返回包含此集合中所有元素指定类型的数组
T[] toArray(T[] a);
// 添加到末尾
boolean add(E e);
// 删除指定元素
boolean remove(Object o);
// 是否包含指定元素
boolean containsAll(Collection> c);
// 添加集合
boolean addAll(Collection extends E> c);
// 按集合删除
boolean removeAll(Collection> c);
// 将删除的条件对外提供
default boolean removeIf(Predicate super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
// 包含指定元素
boolean retainAll(Collection> c);
// 清空集合
void clear();
boolean equals(Object o);
int hashCode();
// 分隔
default Spliterator spliterator() {
return Spliterators.spliterator(this, 0);
}
// Stream流
default Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}
retainAll
表示只保留参数容器中的元素,其他元素则会删除。Java 8对Collection
添加了几个默认方法,包括removeIf
、stream
、splierator
等。
抽象类AbstractCollection
对一些方法提供了默认实现,实现的方式是通过迭代器方法逐个操作。我们可以来看一下这几个方法:
public boolean isEmpty() {
return size() == 0;
}
public boolean contains(Object o) {
Iterator it = iterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}
public Object[] toArray() {
// Estimate size of array; be prepared to see more or fewer elements
Object[] r = new Object[size()];
Iterator it = iterator();
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) // fewer elements than expected
return Arrays.copyOf(r, i);
r[i] = it.next();
}
return it.hasNext() ? finishToArray(r, it) : r;
}
public boolean remove(Object o) {
Iterator it = iterator();
if (o==null) {
while (it.hasNext()) {
if (it.next()==null) {
it.remove();
return true;
}
}
} else {
while (it.hasNext()) {
if (o.equals(it.next())) {
it.remove();
return true;
}
}
}
return false;
}
public boolean containsAll(Collection> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}
public boolean addAll(Collection extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
public boolean removeAll(Collection> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
public boolean retainAll(Collection> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
5.2 List
List
表示有序可重复支持按索引访问的集合,它继承了Collection
,增加了以下方法:
boolean addAll(int index, Collection extends E> c);
default void replaceAll(UnaryOperator operator) {
Objects.requireNonNull(operator);
final ListIterator li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
default void sort(Comparator super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator listIterator();
ListIterator listIterator(int index);
List subList(int fromIndex, int toIndex);
static List of() {
return ImmutableCollections.List0.instance();
}
static List of(E e1) {
return new ImmutableCollections.List1<>(e1);
}
static List of(E e1, E e2) {
return new ImmutableCollections.List2<>(e1, e2);
}
static List of(E... elements) {
switch (elements.length) { // implicit null check of elements
case 0:
return ImmutableCollections.List0.instance();
case 1:
return new ImmutableCollections.List1<>(elements[0]);
case 2:
return new ImmutableCollections.List2<>(elements[0], elements[1]);
default:
return new ImmutableCollections.ListN<>(elements);
}
}
static List copyOf(Collection extends E> coll) {
if (coll instanceof ImmutableCollections.AbstractImmutableList) {
return (List)coll;
} else {
return (List)List.of(coll.toArray());
}
}
这些方法都与位置相关,容易理解,不做阐述。Java 8对List
接口增加了几个默认方法,包括sort
、replaceAll
和spliterator
;Java 9增加了多个重载的of
方法,可以根据一个或多个元素生成一个不变的List
,具体不介绍,可以看API文档。
5.3 Random Access
RamdomAccess
的定义为:
public interface RandomAccess {
}
没有定义任何方法,这是为什么呢?因为RandomAccess
是一个Marker interface
标记接口,用于代表类的一种属性或者说类具备某种功能。
例如,在一些底层实现中会去判断接口有没有实现RandomAccess
,如果实现了RandomAccess
会使用索引进行查找,反之使用迭代器。比如Collections
类中的binarySearch
方法。
6. 相关方法
6.1 Arrays.asList
Arrays.asList
用于将一个数组转换为一个List
集合,需要注意的是的返回的这个List
集合并不是ArrayList
对象,而是Arrays
内部的ArrayList
对象,它表示一个不允许发生结构性变化的ArrayList
对象,比如对这个返回的对象调用add
方法,会发生java.lang.UnsupportedOperationException
异常,代码示例如下:
String[] names = new String[]{"zs", "ls", "ww"};
ArrayList nameList = Arrays.asList(names);
nameList.add("zl");
Arrays
内部的ArrayList
继承了AbstractList
,它表示一个不可修改的列表,如果想要数组转换后的ArrayList
是可变的或者说是可修改的,可以这么做:
String[] names = new String[]{"zs", "ls", "ww"};
ArrayList nameList = new ArrayList(Arrays.asList(names));
nameList.add("zl");
在这个内部类中,内部的数组使用的就是传入的数组,没有拷贝,也没有动态改变大小,对数组的修改也会反应到List
当中。
6.2 ArrayList.toArray
Arraylist.toArray()
可以将ArrarList
对象转换成Object
数组:
ArrayList names = new ArrayList();
names.add("zs");
names.add("ls");
names.add("ww");
Object[] objs = new Object[names.size()];
objs = nameList.toArray();
如果希望将ArrayList
对象转换后的数组是初始化ArrayList
时候指定的泛型类型,可以这么做:
ArrayList names = new ArrayList();
names.add("zs");
names.add("ls");
names.add("ww");
String[] objs = new String[namesList.size()];
objs = nameList.toArray(new String[0]);
7. 特点分析
- 支持随机访问,如果按照索引查找内容,速度是O(1),一步到位
- 不是一个线程安全的集合,考虑线程安全可以使用
Vector
,CopyOnWriteArrayList
,Vector
与ArrayList
实现类似,使用synchronized
实现了线程安全