Java容器遍历——迭代器底层源码详解

目录

1.简介——容器/集合的迭代器

2.非使用迭代器遍历时易产生的错误

2.1错误1——运行不报错,但结果不符合期望

2.2错误2——运行报错

3.迭代器源码解析

4.总结

1.简介——容器/集合的迭代器

        迭代器模式是一种设计模式,是用于设计一种用于遍历某个容器或者某个集合的类,使遍历操作和与之相关的容器或集合的数据结构分离开来。总而言之,如果我们自定义一个容器/集合类,我们应该在这个类的方法内设计一个迭代器方法,用于遍历该容器/集合的元素。

2.非使用迭代器遍历时易产生的错误

2.1错误1——运行不报错,但结果不符合期望

List<Integer> a = new ArrayList<Integer>();
a.add(0);
a.add(1);
a.add(2);
for(int i=0;i<a.size();i++)
{
	if(i==0)
		a.remove(i);
	if(i==1)
		a.remove(i);
}

        按照本意我们是进行三轮循环,然后删除容器中加入的“0”和“1”的,但是结果是只进行了两轮循环,删完后容器中剩下“1”,把“0”和“2”删除了。原因如下:初始容器中的元素按照(索引,值)格式为(0,0)(1,1)(2,2),第一轮循环时,把第一个元素给删除了,注意,此时剩下的元素位置都往前挪了,变成了(0,1)(1,2),同时容器的size也从3变成了2,然后i变成了1,进行下一轮循环,由于i是1,删去的元素是(1,2),容器的size从2变成了1,i变成了2,跳出循环,容器剩下一个(0,1)。综上,出错原因就是size是会因为remove发生变化的,同时元素的索引值也会因为remove发生变化

2.2错误2——运行报错

List<String> strs = new ArrayList<String>();
	strs.add("HIT");
	for(String s :strs)
	{
		if(s.startsWith("HIT"))
			strs.remove(s);
	}

         原因是在使用for-each循环时,会自动调用迭代器(可以先看迭代器源码解析再看这里),儿2.1中是用下标进行循环,不会自动调用迭代器,不会出错。——想验证的话自行复制代码进行debug。

3.迭代器源码解析

首先确定一个观点,那就是调用List迭代器内部引用的List和调用的List是同一个List。

调用List的迭代器:

List<Integer> a = new ArrayList<Integer>();
Iterator b = a.iterator();

调用List的迭代器时,是把a指向的List传给迭代器内部的,也就是说迭代器b内部的结构大致如下:

public class Iterator
{
    private final ArrayList<Integer> list;

    public Iterator(ArrayList<Integer> list)
    {
        this.list = list;
    }
}

下面是实际的迭代器源代码(其中部分操作比如初始化List为同一个List被用其它的方式实现,因此在这段源码中看不到,但我们知道a和b是引用同一个List就行):

  • cursor:初始值为0,每次调用一次next()后都会加1,其逻辑意思是cursor始终指向下一个next()返回值的下标。
  • lastRet:初始值为-1,逻辑意思是上一个next()返回的值的下标。
  • expectedModCount:List调用迭代器时List已经被修改的次数(不明白此处先往下看)
  • modCount:List被修改的次数,modCount是List管理的(不明白此处先往下看)

我们实验的代码如下:

 先来通过在29行添加断点看List执行add时发生的改变:

发现List每执行一次add,modCount就会加一。

然后在33行添加断点看List执行remove时发生的改变:

每执行一次remove ,modCount就会加一。

接着在34行添加断点执行set时发生的改变,发现并未修改modCount的值,只是add和remove修改了modCount的值

这三次跟踪执行,让我们得出了一个结论,那就是List的add和remove调用会增加modCount的值。

接着看迭代器中的hasNext方法源代码:

通过比较cursor(该变量的含义是始终指向下一个next()返回值的下标)和List的size值判断有无下一个值,比较好理解。

接着看迭代器中的next方法源代码:

 发现next中有一个checkForComodification的方法,进去看一下:

如果modCount的值不等于expectedModCount的值,就会抛出异常,由前文知道,modCount的值在调用List的add和remove方法时会改变,expectedModCount的值是暂时不变的,也就是说,如果在用迭代器遍历List中的元素时,调用List中的add或remove就会抛出异常。 

接着继续看next方法,发现cursor每次调用next都会加一,在此时你应该可以理解cursor的内涵了。

接下来看迭代器的remove方法:

迭代器的remove方法没有参数,每次只能在调用完next后进行调用,因为在调用next时lastRet记录的是这个next返回的元素的下标,然后remove就会根据lastRet进行删除,然后将lastRet置为-1,因此连续两次调用remove就会在第一个if语句出错。接着看try语块里的代码,它本质上还是以lastRet为下标,调用了List的remove,我们知道List的remove会导致modCount加一,如果checkForComodification里的modCount和expectedModCount不相等就会抛出异常,而且List的remove还会导致元素发生移位,为了解决这两个问题,try语句块里的cursor=lastRet解决了元素移位的问题,expectedModCount = modCount解决了抛出异常的问题。

4.总结

讲至这里,我们从头到尾按顺序分析了:

①非使用迭代器进行容器遍历时进行remove容易出现的错误,因此我们选择用迭代器遍历。

②为什么在使用迭代器时不能用List的remove以及迭代器的remove的底层原理。

综上所述,如果想遍历容器/集合的元素且同时对其进行remove操作时,应该调用其对应的迭代器,并且通过迭代器的hasNext、next、remove进行操作。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值