概述
ArrayList的遍历方式
1、普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
2、增强for循环(foreach用法)
for (String str : list) {
System.out.println(string);
}
3、迭代器(其中之一)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
说明:增强for循环与迭代器方式都调用了ArrayList的iterator方法,另外ArrayList不止iterator方法可以返回迭代器对象,还有两个listIterator方法(包含重载方法),以及一个spliterator方法都返回的迭代器对象
注意:本篇仅分析iterator()方法的相关实现(listIterator()方法、spliterator()方法将在其他文章中进行分析)
iterator()方法分析
public Iterator<E> iterator() {
return new Itr();
}
用于返回一个迭代器对象的方法,迭代器对象可以用来遍历ArrayList的元素,该方法无需传入参数
ArrayList实现了List接口、List接口继承了Collection接口、Collection接口又继承了Iterable接口,在Iterable接口中定义了可作为迭代器的要求,这个iterator()方法会返回一个Iterator对象(迭代器对象表示迭代器),表示实现Iterable接口的类可作为迭代器
这是ArrayList中重写后的iterator()方法,在iterator()方法中,直接返回了一个创建的Itr对象,接下来我们就继续学习Itr类的实现
ArrayList中的Itr类分析
private class Itr implements Iterator<E> {
…………省略很多源码…………
}
Itr类定义在ArrayList的内部,它是作为普通内部类而存在的,Itr类实现了Iterator接口。Iterator接口规范了作为一个迭代器应该具备哪些能力,我们马上先学习Iterator接口
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());
}
}
Iterator接口规范了作为迭代器对象应该具备的功能,它是一个范型接口
1、hasNext()方法
判断是否有更多元素的方法,如果存在更多元素则会返回true,否则就会返回false
2、next()方法
遍历过程中返回一个元素的方法,next方法在第一次被调用时,会返回第一个元素,注意该方法在ArrayList没有元素时被调用,规范要求会抛出一个NoSuchElementException对象
3、JDK1.8新增的default方法:remove方法
该方法是用于移除元素,默认实现则是抛出UnsupportedOperationException对象
4、JDK1.8新增的另一个default方法
forEachRemaining方法,该方法用于干什么的???没明白!!当为其传入的Consumer为null时,会抛出NullPointerException
Itr构造方法分析
Itr() {}
用于创建对象,一个无参数的构造方法
Itr对象持有的实例变量
int cursor;
// index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
1、cursor代表返回元素的下标,默认值是0
2、lastRet代表最后一次返回元素的下标,-1说明没有元素被返回
3、expectedModCount代表预期改变数量,它的默认值是ArrayList对象持有的modCount的值
hasNext()方法分析
public boolean hasNext() {
return cursor != size;
}
位于Itr类中的hasNext()方法,用于判断是否还有下一个元素
size是ArrayList对象持有的元素总数,在hasNext()方法的内部将Itr对象持有的cursor与size进行比较,不相等代表还有未遍历的元素,此时该方法会返回true,如果cursor与size相等,说明没有更多元素可用于遍历,此时整个hasNext()方法会返回false
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];
}
位于Itr类中的next()方法,用于返回下一个元素,第一次调用该方法时,将返回ArrayList中的第一个元素
1、fail-fast机制,检查是否在多线程下使用ArrayList
开始先执行了checkForComodification()方法,执行fail-fast机制的检查,确保ArrayList未在多线程下使用,未通过检查,会在checkForComodification()方法中抛出ConcurrentModificationException对象,
2、定义局部变量保存cusor值
Itr对象持有的cursor赋值给局部变量i保存
3、检查局部变量i的值是否超出范围
对局部变量i进行检查,若i的值大于等于元素总数size,此处会抛出NoSuchElementException对象(这也是建议先使用hasNext()方法判断是否有未遍历完元素的原因,没有元素时会抛该异常)
4、定义局部变量,用于保存外部类ArrayList对象持有的底层数组对象
接着获取当前ArrayList对象持有的实例变量elementData(底层数组对象),然后赋值给局部变量elementData保存
5、检查cusor值是否大于等于ArrayList的底层数组对象容量,防止ArrayList在多线程下的错误使用
再一次做容错保护,局部变量i的值大于等于底层数组对象的长度length时会抛出ConcurrentModificatoinException对象,
6、更新遍历元素后的游标
为Itr对象持有的cursor执行加1操作(在当前cursor值基础上)
7、存储最后一次遍历元素的下标值
先将即将要返回元素的下标值保存到Itr对象持有的实例变量lastRet中
8、从lastRet下标处,取出元素,并强制类型转换为指定的参数类型E上
接着从局部变量elementData数组对象的指定下标处lastRet处取出元素对象,并且又做了一个强制的向下转型为参数类型E的对象(因为elementData指向的是一个Object数组对象,每个元素的类型是Object),最后就是return元素对象
checkForComodification()方法分析
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
位于Itr类中的checkForComodification()方法,用于检查modCount值与expectedModCount是否相等,可以防止ArrayList在多线程使用,每当modCount与expectedModCount不相等时,就会抛出ConcurretnModificationException对象
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();
}
}
位于Itr类中的remove()方法,重写了Iterator接口中定义的default的remove()方法,用于移除遍历过的最后一个元素
1、检查lastRet值
若lastRet小于0时,说明ArrayList并没有遍历过任何元素,lastRet的默认值是-1,或者ArrayList压根就没有持有元素,此处针对lastRet小于0的情况,会抛出一个IllegalStateException对象
2、检查fail-fast机制,防止多线程下使用ArrayList
调用Itr中的checkForComodification()方法的执行(搜索本文即可)
3、通过两个检查后,获取当前ArrayList对象,并调用ArrayList自身的remove()方法(在删除元素文章中的remove()方法),并为其传入Itr对象持有的lastRet值
4、更新Itr对象持有的cursor值
成功删除元素后,更新Itr对象持有的cursor值为lastRet值(因为后继元素已经前移,所以cursor需要回退为lastRet值)
5、重置lastRet值
将lastRet值重置为-1,这里充分说明Itr对象的remove()方法绝对无法连续调用两次,因为lastRet为-1时,将会抛出IllegalStateException对象。
6、更新用于fail-fast机制用的expectedModeCount值
接着更新Itr对象持有的expectedModeCount值,为ArrayList对象持有的modCount值,remove()操作已经改变了modCount值,这也是为何Itr的remove()方法不能在多线程下安全删除元素的原因。
7、捕获可能抛出的IndexOutOfBoundsException对象
在这里为什么要捕获一个IndexOutOfBoundsException对象呢?因为ArrayList的remove()方法,有一个rangeCheck()方法,这个rangeCheck()方法会在下标不符合要求时抛出IndexOutOfBoundsException对象,此处捕获该异常对象后,会再抛出一个ConcurrentModificationExcepton对象,因为下标值出现错误的原因是,在多线程下对ArrayList进行了并发修改
forEachRemaining()方法分析
@Override
@SuppressWarnings("unchecked")
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();
}
位于Itr类中重写的forEachRemaining()方法,Itr实现的Iterator接口中定义了default的forEachRemaining方法!传入的参数为一个Consumer对象
1、检查传入的参数consumer
使用Objects的静态方法requireNonNull检查传入的consumer对象是否为null,对传入的对象是否要求的,必须不能为null,否则抛出异常
2、定义局部变量存储Array对象当前持有元素的总数
获取当前ArrayList对象持有的size,size表示元素总数,暂时由final修饰的一个同名的局部变量size保存
3、定义局部变量保存当前Itr遍历元素后的游标值
局部变量i负责保存当前Itr对象持有的cursor值
4、检查游标值cursor
做一个游标值的容错保护,局部变量i表示的游标值如果大于等于size值,直接调用return,此时整个方法运行结束
5、定义局部变量保存ArrayList对象持有的底层数组elementData
接着获取当前ArrayList对象持有的底层数组对象elementData,并交给final修饰的同名的局部变量elementData变量保存(为啥作者喜欢用同名啊,多容易让人懵逼)
6、检查游标值是否超出范围
接着对局部变量i的值进行检查,如果i值大于等于elementData数组的长度时,表示游标值超出范围,通过抛出ConcurrentModificationException对象用于提示用户不要在多线程下使用ArrayList
7、将ArrayList持有的元素对象一个一个的传递到传入的Consumer对象中
开始while循环,在while的代码块中,则回调了Consumer对象的accept()方法,每次都会传入一个ArrayList持有的元素对象(做了强转),while循环的终止条件有两个:
a、一个是局部变量i与size相等
b、另一个是modCount不等于expectedModCount时
只要有一个条件不达标,终止while循环!
8、打扫战场
while循环结束后,则是一个什么操作?(作者注释:在迭代结束时更新一次,以减少堆写流量)……更新Itr对象持有的cursor值为局部变量i的值,更新Itr对象持有的lastRet的值为i-1
9、再一次执行fail-fast机制,防止多线程下使用ArrayList
最后又执行了一次checkForComodification()方法
Consumer是一个接口!这个forEachRemaining()方法到底是干甚的?为剩余的元素执行一个特殊的业务逻辑,去实现Consumer接口确实可以办的到,拭目以待!
Objects的requreNonNull()方法
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
用于检查某个对象是否为null,为null,直接抛出NullPointerException对象,不为null则直接返回传入的对象
最后
以上就是忧伤鸵鸟为你收集整理的Java之ArrayList源码分析(第五篇:遍历元素)的全部内容,希望文章能够帮你解决Java之ArrayList源码分析(第五篇:遍历元素)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复