设计模式大总结(五):迭代器模式

前言

说到迭代器,所有的Java开发者都不陌生,例如HashSet,当我们需要遍历HashSet的时候,通过iterator不停的next就可以循环遍历集合中的所有元素。但是这么做到底有什么好处呢?

1、使用者不需要关心HashSet内部的实现,不关心遍历的规则(正序倒序等)

2、正因为使用者不需要关心HashSet的内部实现,所以设计者可以随意更改遍历的规则,对使用无影响。

回想一下我们之前分析过HashMap的内部实现,实际上和HashSet是差不多的,面对如此多的代码,我们只需要使用iterator能够遍历就可以了。

正文

使用迭代器模式,我们可以自己定义接口,但是在JDK中已经有定义好的迭代器接口,可以满足大部分的使用场景:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

一般情况,我们只需要实现hasNext和next方法就可以了。

接下来我们就下一个简单的例子:

定义自己的集合MyCollection,通过迭代器iterator,按照正序返回集合中的对象。

首先,定义我们的MyCollection类:

/**
 * Created by li.zhipeng on 2017/9/21.
 *
 *      自定义集合类
 */

public class MyCollection<T>{

    private List<T> list = new ArrayList<>();

    public void add(T item){
        list.add(item);
    }

    public void remove(T item){
        list.remove(item);
    }

    public Iterator<T> iterator(){
        return new MyIterator(list);
    }

    /**
     * 自定义迭代器
     * */
    private class MyIterator implements Iterator<T>{

        private List<T> dataSource;

        /**
         * 当前位置的指针
         * */
        private int curPos;

        public MyIterator(List<T> data){
            this.dataSource = data;
        }

        @Override
        public boolean hasNext() {
            return curPos < dataSource.size();
        }

        @Override
        public T next() {
            T item = dataSource.get(curPos);
            curPos ++;
            return item;
        }
    }
}

为了简单方便,我在MyCollection内部使用了ArrayList,然后封装了两个方法,添加和删除ArrayList中的元素,iterator()方法返回我们自定义的迭代器,这里还使用了泛型,规定了添加元素的类型,MyCollection的泛型肯定是要和迭代器是一样的,这里就不多说了。

请注意这里的迭代器最好不要公用,尽量返回一个新的迭代器对象,因为如果多线程同时遍历迭代器,这里可能会影响到迭代器的指针,所以我每次都是创建一个新的迭代器。如果你更加关心多线程的并发操作,这里最好每次创建Iterator的时候,应该加入同步锁,传入List的副本,提高多线程并发的安全性。

然后在MyCollection中定义了内部迭代器MyIterator,有一个私有属性curPos,记录遍历的位置指针。

使用private修饰迭代器的原因

之前说过Iterator需要隐藏集合遍历时获取元素的规则,并且这个Iterator也只为我们当前的MyCollection服务,所以公开这个类是一个不明智的选择,我这里选择隐藏这个类,只能通过MyCollection的iterator方法来获取。

大部分的迭代器模式开发中,往往都是隐藏自定义迭代器。

定义完毕,测试一下我们的MyCollection:

public void test(){
        MyCollection<Student> collection = new MyCollection<>();
        collection.add(new Student("1111"));
        collection.add(new Student("2222"));

        Iterator<Student> iterator = collection.iterator();
        while (iterator.hasNext()){
            Student student = iterator.next();
            System.out.println(student.getName());
        }
}

这里写图片描述

确实是按照我们添加的顺序打印了Student的名字。

当我还沉迷于自己的才华无法自拔的时候,产品君说了:我们现在要改成倒序遍历了,你抓紧时间弄一下,明天我们要上线。

如果我之前使用的是for循环,那么我就需要根据功能逻辑,找到遍历的代码,然后一处一处的修改,如果有个100使用的地方,我觉得今晚是没法睡了。

但是机智如我,我早就看穿了产品君骚动的内心,早早的使用了迭代器模式,我只需要对MyIterator做一点小调整:

/**
     * 自定义迭代器
     * */
    private class MyIterator implements Iterator<T>{

        private List<T> dataSource;

        /**
         * 当前位置的指针
         * */
        private int curPos;

        public MyIterator(List<T> data){
            this.dataSource = data;
            // 把遍历开始的指针,放到最后一个
            curPos = data.size() - 1;
        }

        @Override
        public boolean hasNext() {
            // 修改是否遍历的结束条件,这里需要大于0
//            return curPos < dataSource.size();
            return curPos >= 0;
        }

        @Override
        public T next() {
            T item = dataSource.get(curPos);
            // 这里要把位置--
//            curPos ++;
            curPos--;
            return item;
        }
    }

这里写图片描述

运行完美~

总结

经过Demo的实际使用,我们看到了迭代器模式是对于集合遍历规则的封装,当一个集合类被大量使用,而这个时候需要修改遍历规则,如果没有迭代器模式,对于开发者来说是多么的蛋疼。

如果集合类需要多种遍历规则,你也可以指定多套迭代器,这个大家按需开发就好,之后的维护我们就能深刻感受到,几行代码就轻松解放我们的双手的快感。

今天的内容就到此结束了,有问题欢迎留言讨论,一起进步。

猜你喜欢

转载自blog.csdn.net/u011315960/article/details/78053929