文章目录
JDK源码——ArrayList(JDK8)
ArrayList,一个可扩容的,底层用数组实现的,非线程安全的集合类,今天我们来细致看一看源码
1.类的定义
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
Java涉及到容器类的定义,就必须拿出Java中容器的“族谱”
上图是对Java中容器的一个分类,整体分为两大类,单值的Collection和双值的Map,其中各自部分又有针对于不同数据结构的分组,今天谈的ArrayList就是一个线性表List
而在Java基础专栏的接口和抽象类部分曾提到过一个接口和抽象类合作使用的范式:
- 定义接口声明规范和能力,这里针对于Collection就有Collection接口,针对List就有List接口,他们都声明了实现这个接口的类需要拥有的能力也就是方法
- 定义抽象类部分实现一些方法,这里就针对于Collection有AbstractCollection接口,针对List有AbstractList接口,他们负责将一些不怎么会变动的方法给出默认的实现
有了以上理解,我们再来看具体的继承关系
我们发现,像图谱中描述的一样,每一个像List,Set,Queue这样的二级分类都存在一个接口他们继承了Collection接口,并且也都有一个实现了部分方法的抽象类实现接口并继承抽象类AbstractColletion,AbstractCollection为AbstractList实现了部分方法,AbstractList又为它的子类们实现了部分方法,层层的设计将各个层次的分工更好的抽离开了
因此,也就理解了我们在ArrayList和List之间架设一层AbstractList,在AbstractList和Collection之间假设一层AbstractCollection的原因,不过这里还有一个问题:为什么ArrayList已经继承了AbstractList还要实现List接口?
对于这一点,StackOverflow上有个Google的员工询问了Java集合框架的创始者Josh Bloch,Josh Bloch认为这是一个mistake,当时以为这样做有用,只能说大神也不是万能的,不过万能的网友也找出了这样做所带来的意料之外的好处:
- 增加了阅读性
- 便于List创建动态代理对象
讨论的链接如下:Why does LinkedHashSet
extend HashSet
and implement Set
再回到主题,剩下的几个接口Cloneable和Serializable就不用多介绍了,一个是声明需要覆写clone方法,一个是声明可序列化,其中RandomAccess接口也是一个声明性质没有实质内容的接口,是为了声明ArrayList可以是顺序表可以随机访问
2.常量
private static final int DEFAULT_CAPACITY = 10;// 初始容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 最大可申请的容量
private static final Object[] EMPTY_ELEMENTDATA = {};// 一个为空的ArrayList数组实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 和上面那个空数组做区分,作用不同
对于这两个空数组,我们提前下结论:
-
第一个目的是提供返回的空数组,使得返回的空实例的对象都指向同一个;
-
第二个目的是提供初始化使用的默认空间大小为10的空数组
这两个空数组后面方法中会出现印证,对作用的理解会更为深刻具体
4.成员变量
transient Object[] elementData;
这个就是存储当前顺序表的所有元素的实际容器了,两个问题:
-
为什么不是private修饰的?源码中注释给出了答案,是为了简化嵌套类的访问,在Java基础专栏中讲内部类时讲过对于外部类私有变量的访问JVM都会默认生成静态的方法返回,而不定义成private就会省去这一个过程,仅此而已
-
为什么要被transient修饰?transient关键字组织了默认的序列化反序列化过程,ArrayList中自定义了序列化和反序列化的方法readObject和writeObject,而不使用默认序列化过程是因为一般情况下这个数组装不满,默认的序列化过程会把整个数组序列化包括那些空的空间,这样会浪费空间和时间
private int size;// 元素数量
包含元素的容量,后续扩容和返回size的操作
5.构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
空构造,把之前提到的那个用于初始化的数组赋值给elmentData
public ArrayList(int initialCapacity) {
if(initialCapacity>0) {
this.elementData = new Object[initialCapacity];
} else if(initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
指定初始大小的构造方法,小于0直接报错;等于0初始化为空数组,区别于DEE数组,这个代表容量为0的空数组,那个代表没有被赋值过大小为10的数组;大于0就直接按大小新建了
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if((size = elementData.length) != 0) {
/*
* defend against c.toArray (incorrectly) not returning Object[]
* (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
*/
if(elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
给定集合的初始化,要求是这个集合保存的数据类型与ArrayList保存的数据类型相同或者是ArrayList保存类型的子类
实现逻辑:
-
先调用toArray集合变数组赋值给elmentData
-
然后进行特殊情况考察:看数组长度是不是0,是0就构造空ArrayList;不是再判断是不是Object[],这一步判断的原因注释也给出了解释,toArray方法返回的不一定是Object数组,如果给出的不是Object数组的话需要转变成Object数组再赋值给elmentData
6.普通方法——扩容
对于ArrayList的扩容,分成两个部分的函数,一个部分就是检查,检查当前需不需要扩容以及扩容到多大;第二个部分就是真正根据大小进行扩容的函数
6.1检查
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
以上四个函数,首先是暴露的ensureCapacity方法,传入的是想要达到的最小容量
- 检查是不是上面提到的刚初始化好的为默认容量10的数组,是的话minExpand为10,不是就是0
- 然后看传入的minCapacity和minExpand的大小,只有传入的值大的时候才进行真正的扩容
然后就到了ensureExplicitCapacity,传入的和上一个方法一样
-
首先是一个modCount自增,这里有一个值得关注的点modCount,这个属性是继承自AbstractList的,作用就是记录修改的次数,也就是每一次添加、删除、扩容等其他所有可能改变数组的操作值都会++,相当于是这个ArrayList的版本号,那这样操作的目的何在呢?我们可以先看看下面这段代码
List<String> list = new ArrayList<String>(); list.add("a"); Iterator iterator = list.iterator(); while(iterator.hasNext()){ String str = (String) iterator.next(); list.remove(str); }
很显然,对于ArrayList这个非同步的集合来说,在迭代器遍历读的时候,修改元素肯定会抛出异常,而modCount的作用就是用于辅助Iterator在遍历过程中检查数组是否被人修改,每一次迭代器调用next或者remove的时候都会去检查modCount和最开始遍历的时候的modCount是否一致,而所有可能会出现这种写入操作的地方都需要检查容量,因此可以把这一步++放在这里,具体代码下文看迭代器的部分会细讲
-
然后就是一个判断,确定当前传入的这个目标大小是不是需要扩容操作的,因为每一次扩容操作都是以2倍的形式增加容量的,因此并不是每一次的minCapacity都是需要被操作的,如目前数组长度为20,minCapacity为15就没有意义
另外一个检查方法,是一个内部使用的检查方法,最终还是调用ensureExplictCapacity,只不过这之前的参数是要通过calculateCapacity计算的
对于calculateCapacity函数,传入的是当前的内容数组和传入的目标最小容量
- 如果是刚初始化了的数组DEE,那么就去目标最小容量和默认容量中最大的一个为最终目的容量;否则就直接返回最小容量
6.2扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
- 扩容前容量就是当前数组长度,扩容后增加原容量的1/2
- 如果请求扩容的目标容量大1中计算得出的大小,就以要求的标准扩容
- 如果扩容后容量大于最大数组大小,就调用hugeCapaciry,hugeCapacity的逻辑也很简单,如果minCapacity > 最大数组大小,就返回整数的最大值;如果不是就按规定的最大大小来(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)
- 最后通过Arrays的静态方法进行扩容,这个函数的原理也是创建一个新数组,再把传进去的数组复制进去
至此就实现了ArrayList的扩容机制,以后再所有可能越界的操作前都会先检查再操作,检查过程如果需要就按上述逻辑扩容
7.普通方法——写入
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
添加元素的主要方法
- 调用内部的检查容量函数,确保size + 1这个地方有空间,如果不够就采用6中介绍的方式扩容
- 赋值,并使size++,这里可以知道size位置指向的一直都是空下一次直接赋值并确保下一个位置为空
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
add的重载方法,在指定索引的位置插入元素
- 检查所有是否合理,索引在0和当前ArrayList的size之间就是合法的索引,否则就抛出IndexOutOfBoundsException
- 检查size + 1位置的空间可用
- 讲当前index和往后的元素使用arraycopy往后移动一个位置
- index处赋值
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
集合添加的方法,传入的参数是存储当前容器元素类型及其子类的容器
- c转化成Object数组
- 获取添加后新的容器容量,并进行检查
- 将新添加的元素整个的复制到当前数组的后面
- 改变size大小,返回添加结果
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
指定位置的添加集合,同指定位置添加元素一样
- 检查索引index合法性
- c转化成Object数组
- 获取新容量,进行检查
- 获取需要挪动的部分开始的索引,并进行复制向后挪动需要插入的大小
- 将添加的元素从index处开始赋值
- 更新size大小,返回添加结果
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];
}
修改指定位置元素的值
- 检查索引index合法性
- 获取指定索引位置原始值
- 修改指定位置值
- 返回原始值
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
清空ArrayList
- 修改modCount
- 遍历数组赋空值(失去引用的元素等待gc)
- 更新size
8.普通方法——写出
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
将当前ArrayList有值的部分数组copy返回
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
指定了泛型函数,参数a用于存储ArrayList的数组元素
- 判断数组a的大小是否可以容纳ArrayList的所有元素,如果无法容纳就新建一个数组返回,如果可以继续执行
- 复制数组
- 返回数组a
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
- 检查索引
- 返回元素
9.普通方法——删除
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];
}
删除指定位置的元素,并返回删除的值
- 检查索引
- 递增版本号
- 获取需要删除位置的值
- 计算需要移动的元素的个数,并在需要移动的情况下进行复制移动
- 删除移动后末端多出的一个元素,更新size,并返回删除的值
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
内部使用的私有方法,跳过了边界检查,并且不返回删除的值
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
删除指定元素
- 判断传入对象是否为空,如果为空则遍历删除ArrayList中的空值
- 不为空则遍历删除与该元素equals方法相等的值
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
范围删除fromIndex到toIndex之间的元素,这是一个protected修饰的方法
- 版本号递增
- 计算移动元素的个数
- 复制移动数组
- 删除末端多出的元素并更新size
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
removeAll方法会删除ArrayList中所有包含在c中的元素
- 验证c不为空
- 调用batchRemove方法实际进行处理,以下介绍这个函数
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
removeAll中调用的complement为false,c为传入需要被删除的元素Collection
- 取ArrayList的元素数组,并初始化好参数r、w,默认modified为false修改失败
- try语句块包裹了一个for循环,循环从头开始遍历elementData,因为complement是false,所有if判断条件为c中不包含这个元素执行,最终效果就是把c中不包含的元素依次地从0位置开始填充,在循环正常结束后r应该为size,w应该<=size
- 进入finally块,如果是正常退出r == size,此时第一个if块不会被执行,如果不是正常退出,那么我们会把后续未遍历完地元素直接复制到当前w索引处之后,w更新为这一段未遍历元素移动后地末尾
- 如果w不等于size,说明删除了元素,数组末尾存在无效的值,于是从w处开始直到末尾全部赋值null,等待被垃圾回收,之后更新各个参数,modCount增加改动次数,size更新为w,modified置为true说明删除成功
- 返回modified
10.普通方法——序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
序列化细致的部分不用关注,重点是直到为什么我们不使用默认的序列化方式而要自定义序列化方式,在上述代码中我们也看到了当我们序列化的时候并不是整个对象或者把elementData全部序列化,而是只是着重保存了size和size大小空间内的元素,对于一个容器而言最需要被保存的也就是这一部分数据
在写出时,我们会同时检测modCount是否发生变化
在读入时,我们会提前检验当前数组是否需要扩容
11.迭代器
11.1 Itr
11.1.1声明
private class Itr implements Iterator<E>
成员内部类,实现了Iterator接口
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
这个接口remove和forEachRemaining方法拥有默认实现(remove这个默认实现大多数都要覆写),迭代器就是迭代器模式的Java语言级别的体现,为我们提供了遍历一个容器的统一方法和入口
11.1.2成员变量
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
cursor:当前指针(游标)位置
lastRet:指向上一个元素的位置,-1表示没有赋值
expectedModCount:记录迭代器创建时的modCount,之后会通过和这个记录的值比较来检查是否存在并发写入的问题,因为所有写入操作都会递增modCount
11.1.3方法
public boolean hasNext() {
return cursor != size;
}
检查cursor == size,是不是最后一个元素
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];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
- 检查modCount是否被改动
- 检查cursor是否超出合法范围
- cursor += 1
- 返回i位置的元素,并赋值lastRet为i,这里注意next方法调用后指针后移但是逻辑上的当前元素是返回的值也就是lastRet指向的
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区分
- 首先检查lastRet是不是-1(本质还是在检验cursor是否指向了元素,因为调用了next后lastRet不会为-1)
- 检查modCount
- 使用ArrayList的remove方法将lastRet索引处的元素删除
- 让cursor指向前一个位置的元素,因为remove过程删除后元素会整体左移,所以lastRet指向的就是没删除前cursor指向的
- 而此时lastRet没有指向的对象了,故赋值-1
- 重要的一步,更改expectedModCount为当前的modCount,因为remove修改了modCount
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
这个是在JDK8之后引入的方法,是对剩余元素进行传入的某种操作,而传入某段操作或者逻辑就是函数式编程
- 检查参数是否为空
- 获取size,当前cursor,当前elementData,并检查i的合法性
- 从当前位置遍历剩余元素并调用consumer的accpet处理元素,其中遍历条件中每一次循环都需要检查modCount
- cursor和lastRet更新为当前位置
- 检查modCount
11.2ListItr
11.2.1声明
private class ListItr extends Itr implements ListIterator<E>
ListItr是AbstractList.ListItr的优化版本,继承自Itr,是独属于List的迭代器
11.2.2构造方法
ListItr(int index) {
super();
cursor = index;
}
ListItr只有一个有参构造,传入迭代器指向的初始索引
11.2.3普通方法
public boolean hasPrevious() {
return cursor != 0;
}
由于ListItr可以向前移动指针,故而多了hasPrevious方法,实现逻辑和hasNext一样比较索引
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
返回索引
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
和next相对应,previous返回前一个元素,主体逻辑相同,只不过在赋值i
的时候不是+1
而是-1
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
主体逻辑还是调用ArrayList的set方法,而为什么是lastRet而不是cursor,在之前Itr的时候已经提及了
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
在当前位置添加传入的元素
- 检查modCount
- 利用ArrayList的add方法添加
- 更新cursor,lastRet和expectedModCount
11.3获取迭代器
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
public ListIterator<E> listIterator() {
return new ListItr(0);
}
public Iterator<E> iterator() {
return new Itr();
}
可以通过ArrayList的这三个方式获取不同的迭代器,需要注意的是获取到的迭代器并不是单例而是创建出的实例
12.其他方法
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
调用indexOf来判断元素是否出现
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
indexOf返回元素所在位置的索引,如果没有找到就返回-1
首先进行判空处理,若为空,则和null作比较;若不为空,则和o比较,其中和o比较时采取的是equals方法,最后都没有返回就返回-1
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
和indexOf相比,换了遍历比较的顺序,lastIndexOf从数组末端开始比较
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
- 调用父类的clone函数
- 复制elmentData数组,置modCount为0并返回
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
同样forEach方法仍然是函数式编程加入后的产物,Consumer在之前已经提及过,这里处理流程也大致相似
- 检查action是否为空
- 记录modCount,elementData和size,这里也是一个技巧这些局部变量都使用final修饰,防止发生错误
- 从前到后遍历数组,对每一个元素调用accept方法进行相应处理