设计模式之美学习-行为型-迭代器模式(三十一)

什么是迭代器模式

迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。

一个完整的迭代器模式 设计容器(数组、链表、树、图、跳表)和迭代器

为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类

类图

 

 

 

 迭代器接口定义

// 接口定义方式一
public interface Iterator<E> {
    //是否还有迭代元素
    boolean hasNext();
    //指针向前移动一位
    void next();
    //返回当前指针指向的元素
    E currentItem();
}

// 接口定义方式二
public interface Iterator<E> {
    //是否还有迭代元素
    boolean hasNext();
    //指针向前移动一位 并返回对象
    E next();
}

代码实现

//迭代器模式的实现类
public class ArrayIterator<E> implements Iterator<E> {
    private int cursor;
    private ArrayList<E> arrayList;

    public ArrayIterator(ArrayList<E> arrayList) {
        //当前指针
        this.cursor = 0;
        //迭代容器
        this.arrayList = arrayList;
    }

    @Override
    public boolean hasNext() {
        return cursor != arrayList.size(); //注意这里,cursor在指向最后一个元素的时候,hasNext()仍旧返回true。
    }

    @Override
    public void next() {
        //向前移动一位
        cursor++;
    }

    @Override
    public E currentItem() {
        //犯规当前指向元素
        if (cursor >= arrayList.size()) {
            throw new NoSuchElementException();
        }
        return arrayList.get(cursor);
    }
}

public class Demo {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("xzg");
        names.add("wang");
        names.add("zheng");

        //创建迭代器 并为迭代器设置容器
        Iterator<String> iterator = new ArrayIterator(names);
        while (iterator.hasNext()) {
            System.out.println(iterator.currentItem());
            iterator.next();
        }
    }
}

java容器代码实现

java是容器实现获得迭代器的接口 在类里面new 迭代器

//容器
public interface List<E> {
    //定义获取迭代器接口
    Iterator iterator();
    //...省略其他接口函数...
}

public class ArrayList<E> implements List<E> {
    //...
    //返回迭代器
    public Iterator iterator() {
        return new ArrayIterator(this);
    }
    //...省略其他代码
}

public class Demo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("xzg");
        names.add("wang");
        names.add("zheng");

        //使用  foreach语法糖 编译后也是通过迭代器
        Iterator<String> iterator = names.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.currentItem());
            iterator.next();
        }
    }
}

好处是,将迭代和容器分离,复杂的容器可能会有多种迭代方式,如果都写在容器里面增加融洽的复杂度

如:有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等

迭代过程中删除和新增元素

删除元素

可能导致遍历不到的问题 以上面ArrayIterator 为例子

public class Demo {
  public static void main(String[] args) {
    List<String> names = new ArrayList<>();
    names.add("a");
    names.add("b");
    names.add("c");
    names.add("d");

    Iterator<String> iterator = names.iterator();
    iterator.next();
    names.remove("a");
  }
}

情况一

遍历前删除第一个元素 对遍历结果不会有影响

 

情况二 

当遍历到第二个元素 删除元素a 数组重排 导致遍历不到

 

情况三

遍历过程中 删除指针后面的元素 不会有影响

插入元素 

遍历到b 在a前面插入元素 导致a重复遍历

 

如何解决重复遍历和未遍历不到问题

方案1:遍历过程中不能新增删除元素

方案2:遍历过程中 新增删除元素 遍历报错(java就是采用这种实现)

java的解决方案

在 ArrayList 中定义一个成员变量 modCount,记录集合被修改的次数,集合每调用一次增加或删除元素的函数,就会给 modCount 加 1。当通过调用集合上的 iterator() 函数来创建迭代器的时候,我们把 modCount 值传递给迭代器的 expectedModCount 成员变量,之后每次调用迭代器上的 hasNext()、next()、currentItem() 函数,我们都会检查集合上的 modCount 是否等于 expectedModCount,也就是看,在创建完迭代器之后,modCount 是否改变过

 

public class ArrayIterator implements Iterator {
    private int cursor;
    private ArrayList arrayList;
    private int expectedModCount;

    public ArrayIterator(ArrayList arrayList) {
        this.cursor = 0;
        this.arrayList = arrayList;
        //存储arrayList的expectedModCount
        this.expectedModCount = arrayList.modCount;
    }

    @Override
    public boolean hasNext() {
        //检查集合是否改变
        checkForComodification();
        return cursor < arrayList.size();
    }

    @Override
    public void next() {
        //检查集合是否改变
        checkForComodification();
        cursor++;
    }

    @Override
    public Object currentItem() {
        //检查集合是否改变
        checkForComodification();
        return arrayList.get(cursor);
    }

    private void checkForComodification() {
        //集合发生改变报错
        if (arrayList.modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

//代码示例
public class Demo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("a");
        names.add("b");
        names.add("c");
        names.add("d");

        Iterator<String> iterator = names.iterator();
        iterator.next();
        names.remove("a");
        iterator.next();//抛出ConcurrentModificationException异常
    }
}

为什么通过迭代器可以删除元素

迭代器类新增了一个 lastRet 成员变量,用来记录游标指向的前一个元素。通过迭代器去删除这个元素的时候,我们可以更新迭代器中的游标和 lastRet 值,来保证不会因为删除元素而导致某个元素遍历不到

 

 

 

删除元素下标大于cusor什么都不做

伤处元素下标小于cusor cusor-1  lastRet-1

 

posted @ 2020-04-16 15:33  意犹未尽  阅读(174)  评论(0编辑  收藏  举报