Java基础篇:Iterator迭代器

版权声明:本文为原创文章,如有不足之处可以指出,欢迎大家转载,记得标明出处。 https://blog.csdn.net/a745233700/article/details/83240736

一、什么是Iterator:

迭代器(Iterator)是一个对象,它的工作是遍历并目标序列中的对象,它提供了一种访问一个容器(container)对象中的各个元素的方法,把访问逻辑从不同类型的集合类中抽象出来,又不必暴露该对象内部细节。通过迭代器,开发人员不需要了解容器底层的结构,就可以实现对容器的遍历。由于创建迭代器的代价小,因此迭代器通常被称为轻量级的容器。

常常使用JDK提供的迭代接口进行Java集合的迭代。

        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            String string = iterator.next();
            //do something
        }
在没有迭代器时,我们都是这么进行遍历元素的,如下:

对于数组,我们是使用下标进行处理的P:

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,向它发送"向前","向后","取当前元素"的命令,就可以间接遍历整个集合。

 

二、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():删除迭代器刚越过的元素;

三、各个集合的Iterator的实现:

1、ArrayList的Iterator实现:

在ArrayList内部首先是定义一个内部类Itr,该内部类实现Iterator接口,如下:

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
    int expectedModCount = modCount;
    //do something
}

而ArrayList的iterator()方法实现:

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

所以通过使用ArrayList.iterator()方法返回的是Itr()内部类,所以现在我们需要关心的就是Itr()内部类的实现:

在Itr内部定义了三个int型的变量:cursor、lastRet、expectedModCount。其中cursor表示下一个元素的索引位置,lastRet表示上一个元素的索引位置。

从cursor、lastRet定义可以看出,lastRet一直比cursor少一所以hasNext()实现方法异常简单,只需要判断cursor和lastRet是否相等即可。

public boolean hasNext() {
    return cursor != size;
}

对于next()实现其实也是比较简单的,只要返回cursor索引位置处的元素即可,然后修改cursor、lastRet即可:

       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;      //cursor + 1
            return (E) elementData[lastRet = i];  //lastRet + 1 且返回cursor处元素
        }
       final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

checkForComodification()主要用来判断集合的修改次数是否合法,即用来判断遍历过程中集合是否被修改过。modCount用于记录ArrayList集合的修改次数,初始化为0,每当集合被修改一次(结构上面的修改,内部update不算),如add、remove等方法,modCount + 1,所以如果modCount不变,则表示集合内容没有被修改。该机制主要是用于实现ArrayList集合的快速失败机制,在Java的集合中,较大一部分集合是存在快速失败机制的。所以要保证在遍历过程中不出错误,我们就应该保证在遍历过程中不会对集合产生结构上的修改(当然remove方法除外),出现了异常错误,我们就应该认真检查程序是否出错而不是catch后不做处理。

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

2、ListIterator:

接口Iterator在不同的子接口中会根据情况进行功能的扩展,例如针对List的迭代器ListIterator,该迭代器只能用于各种List类的访问。ListIterator可以双向移动。添加了previous()等方法。如果是List集合,想要在迭代中操作元素可以使用List集合的特有迭代器ListIterator,该迭代器支持在迭代过程中,添加元素和修改元素。

 

四、for循环、forEach、Iterator对比:

相同点:都是用于遍历集合元素的。

不同点:

1、形式差别:

//for循环的形式是:
for(int i=0;i<arr.size();i++){...}

//foreach的形式是:
for(int i:arr){...}

//iterator的形式是
Iterator it = arr.iterator();
while(it.hasNext()){ object o =it.next(); ...}

2、条件差别:

(1)for循环需要知道集合或数组的大小,而且需要是有序的,不然无法遍历;
(2)foreach和iterator都不需要知道集合或数组的大小,他们都是得到集合内的每个元素然后进行处理;

3、多态差别:

(1)for和foreach都需要先知道集合的类型,甚至是集合内元素的类型,即需要访问内部的成员,不能实现多态;
(2)iterator是一个接口类型,可以使用相同方式去遍历不同集合中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口),而且他还能随时修改和删除集合的元素,能够将遍历序列的操作与序列底层的结构分离。迭代器统一了对容器的访问方式。这也是接口的解耦的最好体现。

例如:如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样。

4、效率差别:

(1)采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快。

(2)采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快。

(3)从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合。

5、foreach 和 iterator 的其他区别:

使用foreach循环语句的优势在于更加简洁,更不容易出错,不必关心下标的起始值和终止值,底层由iterator实现的,他们最大的不同之处就在于remove()方法上。

如果在forEach循环的过程中调用集合的remove()方法,就会导致循环出错,因为循环过程中list.size()的大小变化了,就导致了错误。 所以,如果想在循环语句中删除集合中的某个元素,就要用迭代器iterator的remove()方法,因为它的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。

 

参考文章:

https://blog.csdn.net/iamkila/article/details/7266890?utm_source=blogxgwz6

https://blog.csdn.net/chenssy/article/details/37521461

猜你喜欢

转载自blog.csdn.net/a745233700/article/details/83240736