迭代器和foreach


对于任何容器类,都必须有某种方式可以插入元素并且将它们返回。比如对于 LIst,add() 方法是插入元素的操作之一,而 get() 方法是取出元素的操作之一。如果要使用容器,就需要对容器的确切类型进行编程。那么有没有方法在使用容器的过程中不需要关心容器类型,就可以让代码应用于不同类型的容器?

答案是肯定的,方式有两种:迭代器foreach 语法。

1.迭代器

迭代器是一个对象,它的工作就是遍历并选择序列中的对象,而客户端程序员不需要知道序列的底层结构。同时迭代器也被称为轻量级对象,创建它的代价较小。

Java 的 Iterator 只能单向移动,功能如下:

  • iterator() 方法要求容器返回一个 Iterator,并且 Iterator 将准备好返回序列的第一个元素;
  • next() 方法获得序列的下一个元素;
  • hashNext() 方法检查序列中是否还有元素;
  • remove() 方法将迭代器最新返回的元素删除。
/**
     * 迭代器遍历
     * @param its
     */
    public static String display(Iterator<Integer> its) {
        String str = "";
        //判断迭代器中是否还有元素
        while(its.hasNext()){
            //获取下一个元素
            Integer next = its.next();
            str = str + next + ",";
            //删除 next 元素
            its.remove();
        }
        return str;
    }

    public static void main(String[] args) {
        //创建整数集合
        List<Integer> list = Arrays.asList(1, 3, 2, 5, 4);

        //创建不同类型的容器
        ArrayList<Integer> arrayList = new ArrayList<>(list);
        LinkedList<Integer> linkedList = new LinkedList<>(list);
        HashSet<Integer> hashSet = new HashSet<>(list);
        TreeSet<Integer> treeSet = new TreeSet<>(list);

        //使用迭代器打印容器元素
        System.out.println("arrayList:" + display(arrayList.iterator()));
        System.out.println("删除后的 arayList:" + display(arrayList.iterator()));
        System.out.println("linkedList:" + display(linkedList.iterator()));
        System.out.println("hashSet:" + display(hashSet.iterator()));
        System.out.println("treeSet:" + display(treeSet.iterator()));
 }

结果如下:

arrayList:1,3,2,5,4,
删除后的 arayList:
linkedList:1,3,2,5,4,
hashSet:1,2,3,4,5,
treeSet:1,2,3,4,5,

从代码中就可以看出,display() 方法根本不需要关心它所遍历的序列的类型信息,这也正是 Iterator 的独到之处,能够将遍历序列的操作与序列底层的结构分离

当容器通过 iterator() 方法返回 Iterator 对象后,这时候就不需要再为容器中的元素的数量操心了,因为这些事情交给 hashNext() 和 next() 去关心就行了。remove() 方法是用来删除 next() 产生的最后一个元素,所以在调用 remove() 方法前必须先调用 next() 方法。

等等,为啥容器类可以调用 iterator() 方法?接着往下看:

2.Collection 和迭代器

Collection 是所有序列容器的根接口,我们再看一眼它的框架图:

在这里插入图片描述
由于 Collection 接口继承了 Iterable 接口,而 Iterable 接口中有 iterator() 抽象方法,这就意味着实现 Collection 接口就必须实现 iterator() 方法来返回 Iterator 对象。

那么问题来了,如果需要创建一个没有实现 Collection 的类时,让它去实现 Collection 接口是非常麻烦的事情,因为这意味着你需要实现它的所有方法,那么 使用 Iterator 就非常吸引人了,此时,只需要写一个方法然后返回 Iterator 对象就可以了。

public class IteratorTest {

    //创建 int 数组
    private int[] arrays = {1, 2, 3, 4, 5};

    /**
     * 迭代器方法
     * @return
     */
    public Iterator<Integer> iterator() {
        //以匿名内部类的方式返回 Iterator 对象
        return new Iterator<Integer>() {
            private int index = 0;
            @Override
            public boolean hasNext() {
                return index < arrays.length;
            }
            @Override
            public Integer next() {
                return arrays[index++];
            }
        };
    }

    /**
     * 迭代器遍历
     * @param its
     */
    public static String display(Iterator<Integer> its) {
        String str = "";
        //判断迭代器中是否还有元素
        while (its.hasNext()) {
            //获取下一个元素
            Integer next = its.next();
            str = str + next + ",";
        }
        return str;
    }

    public static void main(String[] args) {
        IteratorTest itTest = new IteratorTest();
        System.out.println(display(itTest.iterator()));
    }
}

打印如下:

1,2,3,4,5,

上述代码自定义了 iterator() 方法,并且通过匿名内部类的方式返回 Iterator 对象,并重写了其中的 hasNext() 方法和 next() 方法,从而达到了创建迭代器的目的。

3.ListIterator

这里补充一下 ListIterator,ListIterator 是一个更加强大的 Iterator 的子类型,它只能用于各种 LIst 类的访问。它有以下特点:

  • Iterator 只能向前移动,但是 ListIterator 可以双向移动
  • ListIterator 可以产生迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用 set() 方法替换它访问过的最后一个元素;
  • ListIterator 可以通过 listIterator() 方法产生一个指向 List 开始出的 LIstIterator,还可以通过 listIterator(n) 方法创建一个开始就指向列表索引为 n 的元素处的 ListIterator。
public static void main(String[] args) {
        //创建 list
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        //通过 list 获取索引为 1 的 listIterator 对象
        ListIterator listIterator = list.listIterator(1);
        while(listIterator.hasNext()){
            System.out.println("当前元素:" + listIterator.next() +
                    ",下一个元素索引:" + listIterator.nextIndex() +
                    ",上一个元素索引:" + listIterator.previousIndex() + ";");
        }
        //向前移动
        while(listIterator.hasPrevious()){
            System.out.println("向前移动:" + listIterator.previous());
        }
        //向后移动
        while(listIterator.hasNext()){
            //获取当前元素
            listIterator.next();
            //将元素的值设置为 10
            listIterator.set(10);
        }
        System.out.println("修改后的集合:" + list);
    }

结果如下:

当前元素:3,下一个元素索引:3,上一个元素索引:2;
当前元素:4,下一个元素索引:4,上一个元素索引:3;
当前元素:5,下一个元素索引:5,上一个元素索引:4;
向前移动5
向前移动4
向前移动3
向前移动2
向前移动1
修改后的集合:[10, 10, 10, 10, 10]

代码直接通过 listIterator(1) 方法获取指向索引为 1 的 ListIterator,并通过 nextIndext() 和 previousIndex() 方法来获取当前元素的前一个和后一个元素的索引,通过 hasPrevious() 方法来是否还有上一个元素,通过 previous() 方法来获取当前元素,并指向上一个元素,set() 方法可对当前元素进行修改操作。

4.Foreach

到目前为止,foreach 语法主要用于数组和任何 Collection 对象,这点我们在 数组初始化 和 Java容器 中已经介绍过,这里就不再赘述。

之所以能工作,是因为 Collection 继承了 Iterable 接口,原因上面已经说过了,Iterable 接口被 foreach 用来在序列中移动。因此,任何实现了 Iterable 的类,都可以将它用在 foreach 语句中。

简单示例:

public class IterableTest implements Iterable<Integer> {

    //创建整型数组
    private Integer[] ints = {1, 2, 3, 4, 5};

    /**
     * 实现 iterator 方法
     * @return
     */
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            private int index  = 0;
            @Override
            public boolean hasNext() {
                return index < ints.length;
            }

            @Override
            public Integer next() {
                return ints[index++];
            }
        };
    }

    public static void main(String[] args) {
        //foreach 遍历并打印
        for (Integer i : new IterableTest()) {
            System.out.println(i);
        }
    }
}

打印结果:

1
2
3
4
5

上述代码中,创建了一个去实现 Iterable 接口的类,并实现了其中的 iterator() 方法,该方法以匿名内部类的方式返回了 Iterable 的实例,该匿名内部类可以遍历数组中所有的元素。

foreach 语句可以用于数组或其他任何的 Iterable,但是这并不意味着数组肯定也是一个 Iterable,而任何自动包装也不会发生:

/**
     * 遍历 iterable
     *
     * @param iterable
     * @param <T>
     */
    public static <T> void display(Iterable<T> iterable) {
        for (T t : iterable) {
            System.out.print(t + ",");
        }
    }

    public static void main(String[] args) {
        //创建 Integer 数组
        Integer[] ints = {1, 2, 3};
        //将数组转换成 List 然后调用 display 方法
        display(Arrays.asList(ints));
        //调用 display 方法报错
        //display(ints);
    }

代码尝试将数组当做 Iterable 对象传递给 display() 方法,但是失败了,但是将数组转换成 List 对象传递给 display() 方法成功了。这说明不存在任何从数组到 Iterable 的自动转换,必须手动转换

补充一下,如果数组是基本数据类型,比如 int,那么将其转换为 List 集合然后将其当做 Iterable 参数传递的时候,虽然编译不会报错,但是结果是错误的,因为 Java 中泛型要求是对象类型,而基本数据类型不是对象,需要使用其包装类才行。

小结

本章介绍了迭代器的概念以及基本使用,介绍了 Collection 和 Iterable,foreach 语法和 Iterable 之间的联系,补充说明了 ListIterator 这个可以双向移动的迭代器。

####欢迎关注公众号:柯妞妞

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值