Java集合迭代器

Posted Androider_Zxg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合迭代器相关的知识,希望对你有一定的参考价值。

迭代器模式定义

就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。这意味着迭代器需要提供统一的接口。

普通访问

我们先来看下正常访问集合

访问数组

int array[] = new int[3];    
 for (int i = 0; i < array.length; i++) 
     System.out.println(array[i]);

访问List

List<String> list = new ArrayList<String>();
for(int i = 0 ; i < list.size() ;  i++) 
    String string = list.get(i);

我们可以看出,以上两种方式,我们总是知道集合的内部结构。访问集合元素的代码是和集合本身紧密耦合的。无法将访问遍历逻辑从集合类客户端代码抽离出来。不同的集合会有不用的遍历代码。所以才有Iterator,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构,所有的内部状态都由Iterator来维护。客户端不用直接和集合进行打交道,而是控制Iterator向它发送向前向后的指令,就可以遍历集合。

Java迭代器

  1. java.util.Iterator

先看下迭代器接口的定义

package java.util;
public interface Iterator<E> 
    boolean hasNext();//判断是否存在下一个对象元素

    E next();//获取下一个元素

    void remove();//移除元素

2.Iterable

Java中还提供了一个Iterable接口,Iterable接口实现后的功能是‘返回’一个迭代器,我们常用的实现了该接口的子接口有:Collection、List、Set等。该接口的iterator()方法返回一个标准的Iterator实现。实现Iterable接口允许对象成为Foreach语句的目标。就可以通过foreach语句来遍历你的底层序列。

Iterable接口包含一个能产生Iterator对象的方法,并且Iterable被foreach用来在序列中移动。因此如果创建了实现Iterable接口的类,都可以将它用于foreach中。

Package java.lang;

import java.util.Iterator;
public interface Iterable<T> 
    Iterator<T> iterator();

Java中几乎所有的集合都直接或间接提供了遍历本集合的迭代器实现。其作为内部类存在于集合类内部。下面我们看一个最简单的迭代器实现——ArrayList的迭代器。

ArrayList迭代器

在ArrayList内部类Itr实现了Iterator接口,提供next() hasNext() remove()等方法。先看下维护的几个变量

 private class Itr implements Iterator<E> 
        //下一个元素的位置下标
        int cursor;       // index of next element to return
        //上一个元素的位置下标
        int lastRet = -1; // index of last element returned; -1 if no such
        //预期被修改的次数值,初始值等于modCount
        //modCount是ArrayList维护的变量,当进行list.add()/remove()操作时会修改这个值
        int expectedModCount = modCount;
        .....
        
  • 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];
        

首先进行检查,checkForComodification()是Itr内部函数,如下

        final void checkForComodification() 
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        

检查期待修改次数和真正修改次数modCount是否相等,不相等抛出异常
接下来获取下一个元素位置下标cursor进行判断,逻辑很简单不解释。最终取出元素数据数组中相应值返回并将cursor递增。同时将lastRet递增

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

首先判断lastRet是否小于零,小于零则抛出异常。由于初始值为-1并且只有在next()方法中才操作lastRet变量,所以迭代时不next()而直接remove()会报错,这点也很好理解。然后是进行checkForComodification()。接着调用集合remove()方法。并将expectedModCount重新赋值,这点很重要。这也是为什么通过迭代器遍历List时,使用迭代器remove()方法删除元素没有问题,通过list.remove()删除就会报错。后边会详细解释。

  • 外部调用
        List<String> list=new ArrayList<>();
        list.add("abc");
        list.add("edf");
        list.add("ghi");
        for(Iterator<String> it=list.iterator();it.hasNext();)
        
            System.out.println(it.next());
        

关于删除元素

有这样一个问题,需要我们思考:在迭代时如何正确删除元素

方式一:size()遍历调用list.remove()删除

        list.clear();
        list.add("a");
        list.add("b");
        list.add("b");
        list.add("a");
        list.add("a");
         /**
         * 当remove()一个元素后,后面的的元素会集体向前移动,这样删除掉的元素的下一个
         * 元素会移动到当前位置,但此位置已经循环过了,所以会漏掉该元素的循环,log如下:
         *init[a, b, b, b, a],i=0
         * init[a, b, b, b, a],i=1
         * after delete:[a, b, b, a]
         * init[a, b, b, a],i=2
         * after delete:[a, b, a]
         * result:[a, b, a]
         */
        for(int i=0;i<list.size();i++)
            System.out.println("init"+list.toString()+",i="+i);
            if(list.get(i).equals("b"))
                //list.remove(i); 与list.remove(obj)同效果
                list.remove("b");
                System.out.println("after delete:"+list.toString());
            
        

方式二:迭代器遍历调用list.remove()

ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext())
            Integer integer = iterator.next();
            if(integer==2)
                list.remove(integer);
        

结果:
报ConcurrentModificationException()错误。因为在list.remove()时会修改modCount()值,如下:

    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;
    

modCount就和创建Iterator()时值不相等,那么再次调用Itr.next()时进行checkForComodification()检查,就会报错。可以再回头看下next()代码

方式三:通过Itr.remove()方式删除

        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext())
            Integer integer = iterator.next();
            if(integer==2)
                iterator.remove();   //注意这个地方
        

正常删除,因为在Itr.remove()时会更新expectedModCount值

后记

Java中的集合(Collection Map)中,每一个集合都实现了自己的迭代器,这样外部访问时可以采用统一的接口。而实现却是每一个集合中不同的实现。这种模式值得我们在开发中学习。

以上是关于Java集合迭代器的主要内容,如果未能解决你的问题,请参考以下文章

Java学习集合

Java学习集合

在迭代期间可以变异的可迭代集合

JAVA GUI

java 迭代的陷阱

-集合(9.1Java集合框架9.2具体的集合)