JAVA中Iterator和ListIterator介绍与辨析

本文摘抄至 : chenssy龙盛国际benjaminwhx


摘要 : 迭代对于我们搞Java的来说绝对不陌生。我们常常使用JDK提供的迭代接口进行Java集合的迭代。在使用java集合的时候,都需要使用Iterator。但是java集合中还有一个迭代器ListIterator,在使用List、ArrayList、LinkedList和Vector的时候可以使用。这两种迭代器有什么区别呢?

Iterator  iterator = list.iterator();
    while(iterator.hasNext()){
        Stirng string = iterator.next();
        //do something
    }
代其实我们可以简单地理解为遍历,是一个标准化遍历各类容器里面的所有对象的方法类,它是一个很典型的设计模式。Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。 在没有迭代器时我们都是这么进行处理的。 如下: 对于数组我们是使用下标来进行处理的:
int[] arrays = new int[10];
   for(int i = 0 ; i < arrays.length ; i++){
       int a = arrays[i];
       //do something
   }
对于ArrayList是这么处理的:
List<String> list = new ArrayList<String>();
   for(int i = 0 ; i < list.size() ;  i++){
      String string = list.get(i);
      //do something
   }

对于这两种方式,我们总是要事先知道集合的内部结构,访问代码和集合本身是紧密耦合。无法将访问逻辑从集合类和客户代码中分离出来。同时每一种集合对应一种遍历的方法。客户端代码无法复用。在实际应用中如何需要将上面两个集合进行整合是相当麻烦的。所以为了解决以上问题,Iterator模式腾空出世,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构。所有的内部结构都由Iterator来维护。客户端从不直接和集合类打交道,它总是控制Iterator,向他发送“向前”、“向后”、“取当前元素”的命令,就可以间接遍历整个集合。

以上是对Iterator模式进行简单的说明。下面我们看看Java中Iterator接口,看它是如何来进行实现的。

一、java.util.Iterator


在Java中 Iterator为一个接口,它只提供了迭代的基本规则。在JDK中它是这样定义的 : 对 collection进行迭代的迭代器。迭代器取代了 Java Collections Framework中的 Enumeration。迭代器与枚举有两点不同 :
(1)、迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的中的 collection移除元素。
(2)、方法名称得到了改进。
其接口定义如下 :

public interface Iterator{
    boolean hasNext() ;
    Object next() ;
    void remove() ;
}
其中 : Object next() : 返回迭代器刚越过的元素的引用,返回值是Object。需要强制转换成自己需要的类型 boolean hasNext() : 判断容器内是否还有可供访问的元素。 void remove() : 删除迭代器刚越过的元素 对于我们而言,我们一般只需要使用 next()hasNext()两个方法即可完成迭代。如下:
for(Iterator it = c.iterator;it.hasNext(); ){
    Object o = it.next();
    //do something
}
前面阐述了 Iterator有一个很大的优点就是我们不必知道集合的内部结果、集合的内部结构、状态由 Iterator来维持。通过统一的方法 hasNext()next()来判断。获取下一个元素。至于具体的内部实现我们就不必关心了。 但是作为一个合格的程序员我们非常有必要来弄清楚Iterator的实现。下面就是ArrayList的源码进行分析。

二、各个集合的Iterator的实现


下面就ArrayList的Iterator实现来分析,其实如果我们理解了ArrayList、Hashset、TreeSet的数据结构,内部实现,对于他们是如何实现Iterator也会胸有成竹的。因为ArrayList的内部实现采用数组,所以我们只需要记录相应位置的索引即可,其方法的实现比较简单

2.1、ArrayList的Iterator实现

在ArrayList内部首先是定义一个内部类Itr,该内部类实现Iterator接口,如下:
private class Itr implements Iterator<E> {
    //do something
}
而ArrayList的iterator()方法实现:
public Iterator<E> iterator() {
        return new Itr();
    }
所以通过使用 ArrayList.iterator()方法返回的是 Itr()内部类。所以在我们需要关心的就是 Itr()内部类的实现 : 在 Itr()内部定义三个 int型的变量 : cursorlastRetexpectedModCount。 其中 cursor表示下一个元素的索引位置, lastRet表示上一个元素的索引位置。
int cursor;
int lastRet = -1 ;
int expectedModCount = modCount ;
cursorlastRet一直比 cursor少一个。所以 hasNext()实现方法异常简单。只需要判断 cursorlastRet是否相等即可。
public boolean hasNext() {
            return cursor != size;
        }
对于 next()实现其实也是比较简单的。只要返回 cursor索引位置的元素即可。然后修改 cursorlastRet即可。
public E next(){
     checkForComodification();
         //记录索引位置
     int i = cursor ;
         //如果获取元素大于集合元素个数,则抛出异常
     if(i >= size)
     throw new ConcurrentModificationException();
         //cursor + 1
     cursor = i + 1 ;
         //lasrRet + 1 且返回cursor处元素
     return (E) elementData[lastRet = i];  
}
checkForComodification() 主要用来判断集合的修改次数是否合法。即用来判断遍历过程中集合是否被修改。在文章** ArrayList**中已经阐述。 modCount 用于记录 ArrayList 集合的修改次数。初始化为0,,每当集合被修改一次(结构上面的修改,内部 updata不算 ),如 addremove 等方法, modCount + 1 ,所以如果 modCount 不变,则表示集合内容没有被修改。该机制主要是用于实现 ArrayList 集合的快速失败机制。在Java的集合中,较大一部分集合是存在快速失败机制的。这里就不多说,后面会讲到。所以要保证在遍历过程中不出错误,我们就应该保证在遍历过程中不会对集合产生结构上的修改(当然 remove方法除外),出现了异常错误,我们就应该认真检查程序是否出错而不是 catch后不做处理。
final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
对于 remove()方法的是实现,它是调用 ArrayList本身的 remove()方法删除lastRet位置元素,然后修改 modCount即可。
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();
            }
        }
这里就对 ArrayListIterator实现讲解到这里。

Iterator()和listIterator()


在使用java集合的时候,都需要使用Iterator。但是java集合中还有一个迭代器ListIterator,在使用List、ArrayList、LinkedList和Vector的时候可以使用。这两种迭代器(两种迭代器有时候是不能通用的)有什么区别呢?下面我们详细分析。这里有一点需要明确的时候,迭代器指向的位置是元素之前的位置,如下图所示:

这里假设集合List由四个元素List1、List2、List3和List4组成,当使用语句Iterator it = List.Iterator()时,迭代器it指向的位置是上图中Iterator1指向的位置,当执行语句it.next()之后,迭代器指向的位置后移到上图Iterator2所指向的位置。

首先看一下Iterator和ListIterator迭代器的方法有哪些。
Iterator迭代器包含的方法有:
hasNext():如果迭代器指向位置后面还有元素,则返回 true,否则返回false
next():返回集合中Iterator指向位置后面的元素
remove():删除集合中Iterator指向位置后面的元素
ListIterator迭代器包含的方法有:
add(E e): 将指定的元素插入列表,插入位置为迭代器当前位置之前
hasNext():以正向遍历列表时,如果列表迭代器后面还有元素,则返回 true,否则返回false
hasPrevious() :如果以逆向遍历列表,列表迭代器前面还有元素,则返回 true,否则返回false
next():返回列表中ListIterator指向位置后面的元素
nextIndex() :返回列表中ListIterator所需位置后面元素的索引
previous() :返回列表中ListIterator指向位置前面的元素
previousIndex():返回列表中ListIterator所需位置前面元素的索引
remove():从列表中删除next()或previous()返回的最后一个元素(有点拗口,意思就是对迭代器使用hasNext()方法时,删除ListIterator指向位置后面的元素;当对迭代器使用hasPrevious()方法时,删除ListIterator指向位置前面的元素)
set(E e):从列表中将next()或previous()返回的最后一个元素返回的最后一个元素更改为指定元素e

一、相同点

都是迭代器,当需要对集合中元素进行遍历不需要干涉其遍历过程时,这两种迭代器都可以使用。

二、不同点

1.使用范围不同,Iterator可以应用于所有的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型。
2.ListIterator有add方法,可以向List中添加对象,而Iterator不能。
3.ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。

/** hu*/
public class listergodic {
    public static void main(String[] args) {


        List<String> a = new ArrayList<>() ;
        List<String> b = new ArrayList<>() ;
        a.add("A") ;
        a.add("B") ;
        a.add("C") ;

        b.add("1") ;
        b.add("2") ;
        b.add("3") ;
        b.add("4") ;
        b.add("5") ;


        ListIterator<String> aIt = a.listIterator();
        ListIterator<String> bIt = b.listIterator();

        while(bIt.hasNext()){
            while(aIt.hasNext()){
                aIt.next();
            }
            aIt.add(bIt.next());
        }
        System.out.println(a);
        while(aIt.hasPrevious()){
            System.out.println(aIt.previous()+"***");
        }
    }

4.ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
5.都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。

/** hu*/
public class listergodic {
    public static void main(String[] args) {


        List<String> a = new ArrayList<>() ;
        List<String> b = new ArrayList<>() ;
        a.add("A") ;
        a.add("B") ;
        a.add("C") ;

        b.add("1") ;
        b.add("2") ;
        b.add("3") ;
        b.add("4") ;
        b.add("5") ;


        ListIterator<String> aIt = a.listIterator();
        ListIterator<String> bIt = b.listIterator();

        while(aIt.hasNext()){
            System.out.print(aIt.nextIndex()+"***, ");
            System.out.print(aIt.next()+"***, ");
        }
        System.out.println();
        //逆向遍历为 true
        while(aIt.hasPrevious()){
            //返回上一个索引
            System.out.print(aIt.previousIndex()+"***, ");
            //返回上一个索引对应的值
            System.out.print(aIt.previous()+"***, ");
        }
        System.out.println();
    }

/** hu*/
public class listergodic {
    public static void main(String[] args) {


        List<String> a = new ArrayList<>() ;
        List<String> b = new ArrayList<>() ;
        a.add("A") ;
        a.add("B") ;
        a.add("C") ;

        b.add("1") ;
        b.add("2") ;
        b.add("3") ;
        b.add("4") ;
        b.add("5") ;


        ListIterator<String> aIt = a.listIterator();
        ListIterator<String> bIt = b.listIterator();

        //删除第2个Lsit中为偶数的所有元素
        while(bIt.hasNext()){
            bIt.next();
            if(bIt.hasNext()){
                bIt.next();
                bIt.remove();
            }
        }
        System.out.println(b);
    }
删除链表的第一个节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值