iterator接口源码分析(ArrayList中的实现)

iterator是一个迭代器接口,它里面主要有:

boolean hasNext();
E next();

这两个方法,第一个方法表示迭代器含有更多元素则返回true;否则返回false;

第二个方法是返回迭代器的下一个元素;

其中还有两个实现方法:

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }

接下来我们来看看在不同集合类中是如何去实现迭代器的:

1.ArrayList中的实现方式:

    public Iterator<E> iterator() {
        return new Itr();
    }

我们可以看到在这里使用了内部类:

 private class Itr implements Iterator<E> {
        int cursor;       // 返回下一个元素的索引
        int lastRet = -1; // 返回最后一个元素的索引,如果没有就这样
        int expectedModCount = modCount;

        Itr() {}

接下来我们看一下hasNext():

        public boolean hasNext() {
            return cursor != size;
        }
在这里使用了cursor与size比较返回的boolean值;
        @SuppressWarnings("unchecked")
        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();
        }

这个方法是什么作用呢?就是Fail-Fast机制,就是expectedModCount预期修改的次数,modCount就是实际修改的次数;

在这迭代器中,首先定义了一个expectedModCount=modCount默认值是0;这样通过上面的checkForComodification()方法就可以判断list集合在迭代过程中是否被其他线程修改过,这里的修改就是集合的增删改查;因为这些操作都能引起modCount数值加1的操作,这样就使得迭代失败了。

            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];

这一段需要说一下:

第一:为什么我们要创建一个elementData数组,而不是直接ArrayList.this.elementData[lastRet = i]取值?

这里是为了减少寻址次数,如果不定义,那么判断的时候寻址一次,取值的时候又要寻址一次;

        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();
            }
        }

迭代器的移除方法,这里先不说实现,先问一个问题,既然迭代器也有移除,list本身也有移除,那么他们之间有什么不同呢?

那我们接下来来看一下ArrayList下的remove方法:

    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;
    }

这里有个rangeCheck(index)方法,它是干什么的?

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

我们能够看到,它是当前集合坐标大于等于集合size的时候抛出下标越界异常;接着我们往下看,这里有个我们熟悉的变量 modCount++;在迭代器中我们有与之判断的变量:

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
就是这个方法,如果使用ArrayList的remove方法移除的话那么 modCount就会加1,这样迭代器就会报错,所以说在使用迭代器进行遍历的时候是不能使用移除,添加等功能的;

E oldValue = elementData(index);

是要被移除的元素;

int numMoved = size - index - 1;

这个是计算被移除的元素坐标之后的长度,为什么要减1呢?由于坐标是从0开始的。

        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null;

当移除的不是最后一个元素的时候,就进行数组复制,这里说一下详细的参数含义:

elementData:是原数组;

index+1:原数组复制的起始位置;

elementData:目标数组;

index;目的数组放置的起始位置;

numMoved:复制的长度;

执行完之后将原数组置位null;便于jvm回收;

接下来我们看下迭代器实现的移除方法,看看有什么不一样的。

首先不同的是ArrayList里面的remove需要传参:1)一种是传指针2)一种是传元素

然后我们看他们的内部实现:

try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
可以看到,迭代器的remove首先调用了ArrayList的remove方法,也就是咱们上面说的那些,

那这里哟偶什么不同呢?

1)这里没有进行modCount++;也不能这么说,其实还是有的,那就是调用ArrayList的remove时,在它里面进行的modCount++;

那既然这样,那迭代的时候移除元素,不一样会会使得checkForComodification();方法抛出异常吗?

那我们来看第二个不同:

2)它这里处理的很巧妙,就是最后再重新给expectedModCount赋值,这样就能一直保持一样了,这一点ArrayList中的remove方法却没有这个;

                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;

然后我们还看到一个问题,那就是另两个赋值是干嘛用的?

这就要结合迭代器的next来看了,也就是说:只有当迭代器限制性next之后,才能remove,否则remove会跑出异常,why?

我们来看一下开头的判断:

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

lastRet判断小于0就要抛出异常,而我们默认的值是-1;那么好吧肯定得获取到元素的索引吧,而且你也能看到在上面remove方法中lastRet又重新赋值了-1;这样就可以看出来如果不去执行其他的操作,而直接使用remove是不可能的。

我们再来看一下next方法,它里面有一句这样的赋值:

           return (E) elementData[lastRet = i];

i是什么?就是cursor:下一个元素的索引,而在这里i却是当前的索引,因为在cursor+1之前,就已经赋值给i了,所以这里是当前的索引,也就是0;

到这里你是不是已经看出来上面的问题了,只有每次调用next方法获取当前的索引,你才能够将其移除。

还有一个forEachRemaining方法,这个就不说了,这是jdk1.8加入的一种Lamdba表达式。

到这里我们可以来做几个小例子来巩固一下:

1)看一下下面的程序会不会执行结果是什么?

	public static void main(String[] args) {
		ArrayList<String> a=new ArrayList<String>();
		for(int j=0;j<5;j++){
			a.add("a");
		}
		Iterator<String> iterator = a.iterator();
		while(iterator.hasNext()){
			iterator.remove();
		}
		System.out.println(a.size());
	}
这里执行后会跑出异常:


就是这里,我们没有获取当前的索引;

2)这个会怎么样

	public static void main(String[] args) {
		ArrayList<String> a=new ArrayList<String>();
		for(int j=0;j<5;j++){
			a.add("a");
		}
		for(int i=0;i<a.size();i++){
			if("a".equals(a.get(i))){
				a.remove(i);
			}
		}
		System.out.println(a.size());
	}

这个会正常运行的,但是结果却是存在问题的,这是因为你每次遍历的时候i都加了1,但是你移除之后,原来的元素的索引都产生了变化,即减了1,这样下一次再移除的时候就会从索引1开始,这样变成0的索引就无法删除了,这里考察了那个问题就是remove中的复制:

            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);

3)那增强for循环呢?

	public static void main(String[] args) {
		ArrayList<String> a=new ArrayList<String>();
		for(int j=0;j<5;j++){
			a.add("a");
		}
		for(String b:a){
			if("a".equals(b)){
				a.remove(b);
			}
		}
		System.out.println(a.size());
	}

这个一样是跑出异常的,

首先说一下,增强for循环其实就是迭代器的next方法,这个大哥断点你就可以看到,那么好了我们知道了它调用了迭代器的next方法,此方法里面有个 checkForComodification();校验,第一次是成功的进入了,然后调用ArrayList的remove方法,modCount++了,而ArrayList中的remove有没有最后 expectedModCount = modCount;进行赋值,因此会跑出异常:

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
恩,这样就可以了,那有没有可以一边迭代一边修改的迭代器呢,有的,就是listIterator迭代器,看名字就知道它的作用是list集合,而不是适应所有的集合的。这个之后再说。




  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值