美文网首页
关于Java集合中的迭代

关于Java集合中的迭代

作者: Wangheguan | 来源:发表于2018-04-26 21:15 被阅读0次

    一、接口Iterable和接口Iterator的区分及联系

    【英文释义】
    Iterable:可迭代的,形容词。
    Iterator:迭代器,名词。

    Iterable JDK源码:

    public interface Iterable<T>
    {
        Iterator<T> iterator();//iterator方法用于返回一个实现了Iterator接口的对象
    }
    

    Iterator JDK源码:

    public interface Iterator<E> {
        boolean hasNext(); //每次next之前,先调用此方法探测是否迭代到终点
        E next();            //返回当前迭代元素 ,同时,迭代游标后移
        void remove() ;
    }
    

    以下是笔者对于两者区分及联系的理解:
    (1) 迭代器iterator是用来遍历集合中元素的工具。任何一个集合要想通过迭代的方式遍历元素则必须获得一个属于自身的迭代器;
    (2) 接口Iterable中提供了一种方法为iterator(),该方法用于返回一个实现了Iterator接口的对象,即返回一个迭代器;
    (3) 集合的基本接口Collection继承了接口Iterable,故所有直接或间接实现了接口Collection的集合类都是可迭代的,都可以通过调用.iterator()方法得到一个属于自身的迭代器。


    二、迭代器Iterator及列表迭代器ListIterator

    (一)Java迭代器

    java迭代器中查找一个元素的唯一方法时调用next,在执行查找操作的同时,迭代器位置随之向前移动。因此,应该将java迭代器认为是位于两个元素之间,当调用next时迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。

    向前移动的Java迭代器

    (二)迭代器Iterator

    迭代器Iterator可用于所有集合,包括CollectionMap及其子集合。(其中Map使用迭代器是通过映射的方式得到一个set集合,然后再迭代,具体方式可以见Map的遍历方法,这里不再赘述)。
    Iterator接口包含4个方法:

    public interface Iterator<E>
    {
      E next();
      boolean hasNext();
      default void remove();
      default void forEachRemaining(Consumer<? super E> action);
    }
    

    —hashNext()方法
    如果迭代器对象还有多个供访问的元素,则返回true,否则返回false。
    —next()方法
    反复调用next()方法可以逐个访问集合中的元素,但是到达了集合的末尾,next方法会抛出一个NoSuchElementException。故在调用next方法之前要调用hasNext方法。
    特别注意的是,next方法返回的是一个集合元素。
    —remove()方法
    remove()方法将会删除的是上次调用next方法时返回的元素。并且next方法和remove方法具有互相依赖性。如果调用remove之前没有调用next方法将会是不合法的,会抛出IllegalStateException异常。例如:

    Collection<String> c = .......;
    Iterator it = c.iterator();
    //连续删除两个元素这么做是不合法的:
    it.next();
    it.remove();
    it.remove();//error
    //应该这么做:
    it.next();
    it.remove();
    it.next();
    it.remove();
    

    —forEachRemaining()方法
    在Java SE 8中,可以调用forEachRemaining方法并提供一个lambda表达式(它会处理一个元素)。将对迭代器的每一个元素调用这个lambda表达式,直到再没有元素为止。

    iterator.forEachRemaining(element-> do something with element);
    

    (三)列表迭代器ListIterator

    迭代器ListIterator继承于Iterator,只能用于List及其子集合。
    首先来看看接口ListIterator的JDK源码:

    public interface ListIterator<E> extends Iterator<E> {
      boolean hasNext();
      E next();
      void remove();
      boolean hasPrevious();
      E previous();
      int nextIndex();//返回下一次调用next方法时将返回的元素索引
      int previousIndex();//返回下一次调用previous方法时将返回的元素索引
      void set(E e);//用新元素取代next或previous上次访问的元素,如果在next或previous上次调用之后列表结构被修改了将抛出IllegalStateException异常
      void add(E e);//在当前位置前添加一个元素,注意与Collection的add方法不同
    }
    

    补充:
    List与泛型集合之间有一个重要的区别是,List是一个有序集合,每个对象的位置都很重要,且List是有序可重复的。
    List接口有三个实现集合类,分别是ArrayListLinkedListVector,其中ArrayList底层采用数组存储元素,LinkedList底层采用双向链表存储元素,Vector底层与ArrayList相同(但是它是线程安全的、效率低)
    List接口定义了多个用于随机访问的方法,以下是List接口的部分JDK源码:

    public interface List<E> extends Collection<E> {
      int size();
      boolean add(E e);
      boolean remove(Object o);
      E get(int index);
      E set(int index, E element);
      void add(int index, E element);
      E remove(int index);
      int indexOf(Object o);
    }
    

    —void add(E e)方法
    特别注意的是,接口ListIterator的add()方法返回值类型是void,而Collection接口中的add()方法返回值类型是boolean。
    因此,List.add方法是将对象添加到链表的尾部,如果有时候想要在列表的中间插入元素则显得有点棘手(比如基于数组实现的列表)。由于迭代器是描述集合中位置的,所以这种依赖于位置的add()方法将由迭代器负责。

    List<String> staff = new LinkedList<>();
    staff.add("张三");//Collection中的add方法
    staff.add("李四");
    staff.add("王五");
    ListIterator<String> lt = staff.listIterator();
    lt.next();
    lt.add("赵六");//ListIterator中的add方法
    
    代码执行示意图
    注意:List接口中也有一个add方法,void add(int index, E element),该方法用在此列表的指定位置插入指定的元素(可选操作)。将当前在该位置的元素(如果有)和任何后续元素向右移。前提是知道索引值。
    补充:add方法只依赖于迭代器位置,而remove方法依赖于迭代器的状态。
    —void set(E e)方法
    再来说明一个比较不一样的方法,set方法用一个新元素取代调用next或者previous方法返回的上一个元素,下面代码将用一个新值取代链表的第一个元素:
    List<E> list = new LinkedList<E>();
    ListIterator<E> lt = list.listIterator();
    E oldValue = lt.next();
    lt.set(newValue);
    

    注意:List接口中也有一个set方法,E set(int index, E element),该方法用指定的元素替换此列表中指定位置处的元素,前提是知道索引值。
    —int nextIndex()、int previousIndex()方法
    列表迭代器接口还可以通过int nextIndex()、int previousIndex()方法方法告知当前位置的索引。实际上,从概念上说,由于Java迭代器指向两个元素之间的位置,所以可以同时产生两个索引:
    nextIndex() 返回下一次调用next方法时将返回的元素索引;
    previousIndex() 返回下一次调用previous方法时将返回的元素索引。

    (四)多迭代器

    如果在某个迭代器修改集合时,另一个迭代器对其进行遍历,一定会出现混乱的状况。为了避免并发情况下修改的异常,应遵循以下规则:
    可以根据需要给容器附加许多的迭代器,但是这些迭代器只能读取列表。另外,再单独附加一个既能读又能写的迭代器。


    三、总结

    迭代器Iterator及列表迭代器ListIterator的区别:
    (1)Iterator可以用于所有集合,SetListMap等等及其子集合,ListIterator只能用于List及其子集合;
    (2)ListIterator有add、set方法,能添加、替换对象;
    (3)ListIterator有hasPrevious和previous方法,可以逆向遍历;
    (4)ListIterator可以定位当前索引位置,nextIndex和previousIndex可以实现;

    补充
    Java集合中保存的元素实质是对象的引用,迭代出来的元素都是原来集合元素的拷贝,也就是说迭代出的元素都是引用的拷贝,结果还是引用。
    那么,如果集合中保存的元素是可变类型的,我们就可以通过迭代出的元素修改原集合中的对象。而对于不可变类型,如String 基本元素的包装类型Integer 都是则不会反应到原集合中。可见https://www.cnblogs.com/keyi/p/5821285.html


    以上便是笔者的一些总结,若有不足或不对之处,望诸位不吝指出。

    相关文章

      网友评论

          本文标题:关于Java集合中的迭代

          本文链接:https://www.haomeiwen.com/subject/nirmlftx.html