Hello,大家好:
在上一篇博文中我们讲解了设计模式中的迭代器模式,这一篇文章,咱们来聊聊JDK源码中是如何去实现迭代器模式的。如果对迭代器模式不清楚的同学,请查看这篇文章超详细-设计模式之迭代器模式。
在Java中存储数据的数据结构有很多种,例如Map、数组、列表等等。每种数据结构的遍历方式都不相同,对于使用者来说,我肯定希望能在不知道每种数据结构内部的存储细节的情况下,对每种容器完成遍历。于是Java实现了迭代器模式,定义一种通用的遍历元素的方法,由每种数据结构去实现这些遍历方法。下面我们将从源码的角度分析Java中迭代器模式的实现。
在讲解源码之前,抛出一个小坑,不知道小伙伴们在使用迭代器循环列表时,对列表进行删除操作,会抛出ConcurrentModificationException异常,但是从迭代器中删除元素,也可以删除底层集合中的元素,并且不会抛出异常,示例代码如下:
List<Integer> list= new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
iterator.remove();//不会抛出异常
//list.remove(0); //会抛出异常
}
待会通过大家阅读源码自会有答案。
Iterator接口:
JDK对它的定义为:一个集合的迭代器,迭代器在java集合框架中替代了Enumeration,迭代器和Enumeration由两个不同之处:
注:Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃,但在现代代码中已经被很少使用了。尽管如此,它还是使用在诸如Vector和Properties这些传统类所定义的方法中,除此之外,还用在一些API类,并且在应用程序中也广泛被使用。今天它不是重点。
Iterator方法介绍:
Iterable(java.lang.Iterable) 接口:
是Java集合的顶级接口之一。Collection接口继承Iterable,所以Collection的所有子类也实现了Iterable接口。该接口的核心方法是:Iterator< T > iterator();该方法返回一个Iterator类,用以迭代元素。
上面提到Java集合的所有子类都实现了Iterator接口的方法,用以返回一个迭代器遍历元素,我们以ArrayList的源码为例,来详细的看一下具体存储元素的容器类是如何维护一个迭代器的。下面是ArrayList类中实现Iterable接口的iterator()方法源码:
//该方法是对Iterable接口中定义的方法的实现。返回Itr类
public Iterator<E> iterator() {
return new Itr();
}
以下是Itr源码,Itr类是ArrayList中的一个内部类,因为在迭代器迭代过程中,会访问ArrayList中存储的元素。所以将其定义为一个内部类。
private class Itr implements Iterator<E> {
int cursor; // 下一个元素的索引
int lastRet = -1; // 返回迭代过程中最后一个元素的索引,如果没有返回-1
int expectedModCount = modCount; //让预计修改次数等于修改次数
Itr() {
}
//该方法判断是否还有下一个元素,只是判断当前游标是否等于容器的大小
public boolean hasNext() {
return cursor != size;
}
//该方法通过cursor++的方式进行迭代。lastRet指向迭代过程中最后一个元素。
@SuppressWarnings("unchecked")
public E next() {
//检查元素是否被修改,就是判断expectedModCount的值和modCount是否相等。
checkForComodification();
int i = cursor;//记录当前要迭代的位置
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//将当前游标+1
cursor = i + 1;
return (E) elementData[lastRet = i];
}
//从底层集合中移除lastRet指向的元素,详情看图说明
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//其实是调用ArrayList自身的remove方法
ArrayList.this.remove(lastRet);
//让指针指向删除元素的位置
cursor = lastRet;
lastRet = -1;
//让expectedModCount = modCount,所以在下一次迭代时,不会ConcurrentModificationException抛出异常。
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//对该方法操作代码见后文,这是一个回调方法。
@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();
}
//在遍历过程中,将elementData存储的元素传入给了Custom类的accept方法。
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();
}
//这是文章开头那个坑的答案,总结见文末。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
remove元素细节如下,假设现在调用next()方法,得到元素值为2,删除2号元素后,游标位置如下图所示:
上述操作代码如下:
List<Integer> list= new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
if(iterator.next() == 2) {
iterator.remove();
}
}
System.out.println("list.size="+list.size());
对forEachRemaining()方法使用代码如下:
List<Integer> list= new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Iterator<Integer> iterator = list.iterator();
iterator.forEachRemaining(new Consumer<Integer>() {
public void accept(Integer t) {
System.out.println(t);
};
});
简单介绍一下Consumer接口:表示接受单个输入参数且不返回结果的操作。
阅读源码后,文章开头我们抛出的那个坑,就能够从checkForComodification()方法中得到答案,在使用迭代器模式进行遍历时,如果有线程向集合中添加或者删除元素,那么modcount的值便会修改,使得expectedModCount不等于modCount。从而抛出这个异常。之所以使用迭代器删除元素不会抛出这个异常,是因为迭代器删除元素后会重新让expectedModCount等于modCount,ArrayList添加和删除元素的过程请阅读ArrayList源码解析。
好,ArrayList对迭代器的实现原理到这儿就讲述的差不多啦,有兴趣的小伙伴可以再去看看其他集合类的源码。本文若有不足之处,还请大家多多指正。若有疑问,请小伙伴们私信我,或者评论区留言。更多IT技术资讯和学习资料,请大家关注《炫酷的Java》公众号阅读和下载,谢谢大家。