Java 中iterator和iterable的关系是怎样的

迭代器模式

在容器里存放了大量的同类型对象,我们如果想逐个访问容器中的对象,就必须要知道容器的结构。比如,ArrayList 和 LinkedList 的遍历方法一定是不一样的,如果再加上HashMap, TreeMap,以及我们现在正在研究的BinarySearchTree,对于容器的使用者而言,肯定是一个巨大的负担。作为容器的使用者,我肯定希望所有的容器能提供一个统一的接口,这个接口可以遍历容器中的所有内容,又可以把容器的细节屏蔽掉。

Iterator上定义了各种方法,用于遍历。

例如:

    public static void main(String args[]) {
        LinkedList<Integer> ll = new LinkedList<>();
        for (int i = 0; i < 10; i++)
            ll.add(i);

        Iterator<Integer> ii = ll.iterator();
        while (ii.hasNext())
            System.out.printf("%d\t",ii.next());


        System.out.println();
        ArrayList<Integer> al = new ArrayList<>();
        for (int i = 0; i < 10; i++)
            ll.add(i);

        Iterator<Integer> aii = al.iterator();
        while (aii.hasNext())
            System.out.printf("%d\t",aii.next());
    }

可以看到,我们根本就不需要关心后面具体的数据结构,只需要使用 Iterator,不管后面是数组还是链表,都可以很方便地遍历整个容器。

接口定义:

public interface Iterable<T> {
  Iterator<T> iterator();
}

public interface Iterator<E> {
  boolean hasNext();
  E next();  // 取出下一个元素
  void remove();
}



用Iterator模式实现遍历集合

Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。

例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:、

  for(int i=0; i<array.size(); i++) { ... get(i) ... }

而访问一个链表(LinkedList)又必须使用while循环:

while((e=e.next())!=null) { ... e.data() ... } 

以上两种方法客户端都必须事先知道集合的内部结构,访问代码和集合本身是紧耦合,无法将访问逻辑从集合类和客户端代码中分离出来,每一种集合对应一种遍历方法,客户端代码无法复用。

更恐怖的是,如果以后需要把ArrayList更换为LinkedList,则原来的客户端代码必须全部重写。

解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:

for(Iterator it = c.iterater(); it.hasNext(); ) { ... } 

奥秘在于客户端自身不维护遍历集合的"指针",所有的内部状态(如当前元素位置,是否有下一个元素)都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。

客户端从不直接和集合类打交道,它总是控制Iterator,向它发送"向前",“向后”,"取当前元素"的命令,就可以间接遍历整个集合。

首先看看Java.util.Iterator接口的定义: 
public interface Iterator<E> {
  boolean hasNext();
  E next();  // 取出下一个元素
  void remove();
}

依赖前两个方法就能完成遍历,典型的代码如下:

	 for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); // 对o的操作... } 

每一种集合类返回的Iterator具体类型可能不同,Array可能返回ArrayIterator,Set可能返回 SetIterator,Tree可能返回TreeIterator,但是它们都实现了Iterator接口,因此,客户端不关心到底是哪种 Iterator,它只需要获得这个Iterator接口即可,这就是面向对象的威力。

所有集合类都实现了 Collection 接口,而 Collection 继承了 Iterable 接口。

public interface Collection<E> extends Iterable<E> {  }

而在具体的实现类中(比如ArrayList),则在内部维护了一个Ite内部类,该类

/**
     * An optimized version of AbstractList.Itr
     */
    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;

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

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

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

在遍历元素或者删除元素时,必须要进行next()方法,要不然,迭代器不能正常运行:如

/**
 *太极云软件技术股份有限公司版权所有 1990-2016. http://www.tyky.com.cn
 * @file com.hoojjack.spring_study.tutorialspoint
 *
 */
package com.hoojjack.spring_study.tutorialspoint;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @ClassName: TestIterator
 * @Description: TODO(这里用一句话描述这个类的作用)
 * @author hoojjack
 * @date 2017年5月15日 下午6:42:15
 *
 */
public class TestIterator {
    public static void main(String args[]){
        List<String> list = new ArrayList<String>();
        list.add("h");
        list.add("o");
        list.add("o");
        list.add("j");
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            //it.next();//注释这行会报错,具体原因请看源码
            it.remove();
            break;
        }
        for(String i:list){
            System.out.println(i);
        }
    }
}

为什么一定要去实现Iterable这个接口呢?为什么不直接实现Iterator接口呢?

  看一下JDK中的集合类,比如List一族或者Set一族,都是实现了Iterable接口,但并不直接实现Iterator接口。 仔细想一下这么做是有道理的。

因为iIterator接口的核心方法next()或者hasNext()是依赖于迭代器的当前迭代位置的。如果Collection直接实现Iterator接口,势必导致集合队中中包含当前迭代位置的数据(指针)。当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么next()方法的结果会变成不可预知。除非再为Iterator接口添加一个reset()方法,用来重置当前迭代位置。 但即时这样,Collection也只能同时存在一个当前迭代位置。 而Iterable则不然,每次调用都会返回一个从头开始计数的迭代器。 多个迭代器是互不干扰的。

直接继承Iterator,从Iterator接口可以看出需要子类实现next(),hasNext()方法,假设都交给List去实现,那么获取list的Iterator()时就会出现上面解释那样,而继承Iterable接口,则需要每次List返回一个新的Iterator对象(见ArrayList的new Itr()),因此可以避免上述说到的原因。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值