天天看点

设计模式-迭代器模式及应用

在程序设计中,要访问集合对象时。一般通过for循环、foreach、迭代器等方式。Java中的集合提供都有迭代器,例如 Collection、List、Set、Map 都提供了迭代器,我们只需要获取相关迭代器,就可以对元素进行遍历。

本文讲解的就是迭代器模式,JDK虽然已经提供了迭代器,自己如何实现迭代器模式,来遍历集合或者聚合的对象。

迭代器模式定义与结构

迭代器(Iterator)模式的定义:迭代器提供一个顺序方法指定类型聚合对象的操作,而不暴露聚合对象的内部细节。

1.迭代器模式的结构:

迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。

迭代器模式主要包含以下角色。

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

结构图:

设计模式-迭代器模式及应用

代码案例:

  • 抽象聚合
public interface Aggregate {
	//增加元素
	public void add(Object obj);
	//删除元素
    public void remove(Object obj);
    //获取迭代器
    public Iterator getIterator();
}
           
  • 具体聚合 1
public class LinkedListAggregate implements Aggregate {
 private List<Object> list = new LinkedList<>();
	

	@Override
	public void add(Object obj) {
        list.add(obj);	
	}

	@Override
	public void remove(Object obj) {
		list.remove(obj);	
	}

	@Override
	public Iterator getIterator() {
		 return (new Iterator(list));
	}
}
           
  • 具体聚合 2
public class ListAggregate implements Aggregate{
	private List<Object> list = new ArrayList<Object>();
	
	@Override
	public void add(Object obj) {
        list.add(obj);	
	}

	@Override
	public void remove(Object obj) {
		list.remove(obj);	
	}

	@Override
	public Iterator getIterator() {
		 return (new Iterator(list));
	}
}
           
  • 抽象迭代器
public abstract class AbstractIterator {

	//第一个元素
	protected abstract Object first();
	
	//下一个元素
	protected abstract Object next();
	
	//是否存在下一个元素
	protected abstract Boolean hashNext();
}
           
  • 具体迭代器
public class Iterator extends AbstractIterator {

	//迭代对象
	private List<Object> list = null;
	//下标
	private int index = -1;
	
	//迭代器构造器
	public Iterator(List<Object> list) {
		this.list = list;
	}

	protected Object first() {
		index = 0;
		return list.get(index);
	}

	protected Object next() {
		Object object = null;
		if (hashNext()) {
			object = list.get(++index);
		}
		return object;
	}

	protected Boolean hashNext() {
		if (index < list.size() -1 ) {
			return true;
		}else {
			return false;
		}
	}
}
           
  • 使用方
public class Client {
	public static void main(String[] args) {
		// list 迭代
		ListAggregate listAggregate = new ListAggregate();
		listAggregate.add("a");
		listAggregate.add("b");
		listAggregate.add("c");
		Iterator iterator = listAggregate.getIterator();
		while (iterator.hashNext()) {
			Object next = iterator.next();
			System.out.println(next);
		}
	
		System.out.println("===================");

		// LinkedList 迭代
		LinkedListAggregate LinkedAggregate = new LinkedListAggregate();
		LinkedAggregate.add("1");
		LinkedAggregate.add("2");
		LinkedAggregate.add("3");
		Iterator iterator1 = LinkedAggregate.getIterator();
		while (iterator1.hashNext()) {
			Object next = iterator1.next();
			System.out.println(next);
		}
	}
}
           

执行结果:

a
b
c
===================
1
2
3
           

案例代码结构图:

设计模式-迭代器模式及应用

通过执行结果验证,自定义的迭代器实现了和JDK提供的迭代器类似的功能,可以对集合对象进行迭代。那这里虽然我们清楚ArrayList底层用的是数组,而LinkedList底层数据结构使用了链表的方式,对于我们的迭代器来说,遍历的关键是通过数组或者链表的下标对集合进行遍历。

迭代器模式的优缺点

迭代器模式的优点有很多,主要有点如下;

优点:

  • 访问集合对象时,隐藏了对象内部的表示,使用者只需获取迭代器完成集合对象的迭代;
  • 遍历任务交由迭代器完成,无需关注迭代实现细节;
  • 支持拓展,可以自定义迭代方式;
  • 封装良好,为不同的集合提供统一的迭代器接口。

缺点:

  • 新的数据结构,需要实现不同的迭代器具体实现。增加类的数量

迭代器模式在源码中的使用

1.JDK源码中的使用

因为迭代器模式比较简单,且一般的集合都提供相应的迭代器。这里就看一下JDK是如何实现迭代器的,首先看下迭代器接口 Iterator

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

除了提供两个基本的方法外,还提供两个默认的方法,jdk1.8后的写作方式。然后举例两个常见的集合,看下是如何实现迭代器的方法。

接以

ArrayList

为例。

ArrayList

实现了

List

接口,

List

接口继承了

Collection

接口,

Collection

接口继承了

Iterable

接口,

Iterable

接口中包含了一个

iterator( )

方法,因此

ArrayList

就需要实现该

iterator( )

方法。该方法的实现很简单,就是返回一个实现了

Iterator

接口的迭代器实例。

ArrayList 类中的迭代器是以内部类的形式实现的,由于内部类可以直接访问外部类的成员变量,所以该迭代器内部类可以很方便地实现 Iterator 接口中的 hasNext( ) 和 next( ) 方法。ArrayList 中的迭代器内部类名字是 Itr,

源码如下:

/**
     * 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;

        Itr() {}

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

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
           

Itr

ArrayList

中实现了

Iterator

接口的内部类,且在实现

hasNext()

next()

方法时,可以很方便地访问

ArrayList

的成员变量

size

elementData

数组。

ListItr 是对 Itr的拓展 内部类增加了 hasPrevious() 方法,主要用于判断是否还有上一个元素。

源码:

/**
     * An optimized version of AbstractList.ListItr
     */
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }
           
拓展

1.迭代时改变集合中的元素,为什么会导致异常?

先看下一段代码:

List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

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

Arraylist集合在增删元素后会报错,

怎么确定在遍历时候,集合有没有增删元素呢?在ArrayList中定义一个成员变量

modCount

,记录集合

被修改的次数,集合每调用一次增加或删除元素的函数,就会给

modCount

加1。当通过调用集合上的

iterator()

函数来创建迭代器的时候,我们把modCount值传递给迭代器的

expectedModCount

成员变量,之

后每次调用迭代器上的

hasNext()

next()

currentItem()

函数,我们都会检查集合上的modCount是否等于

expectedModCount

,也就是看,在创建完迭代器之后,

modCount

是否改变过。

如果两个值不相同,那就说明集合存储的元素已经改变了,要么增加了元素,要么删除了元素,之前创建的迭代器已经不能正确运行了,再继续使用就会产生不可预期的结果,所以我们选择fail-fast解决方式,抛出运行时异常,结束掉程序。

上面列举的案例,当遍历下一个元素后,删除集合中a的元素,再去遍历时,

modCount

已经更改,两个值不相等,直接抛出异常。

2.在使用ArrayList迭代器遍历的同时,能否遍历删除其中的元素呢?

看如下代码:

List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        
        Iterator<String> iterator = list.iterator();
        iterator.next();
        iterator.remove();
iterator.remove();
           

这里会报

java.lang.IllegalStateException

异常,

Java迭代器中提供的remove()方法还是比较鸡肋的,作用有限。它只能删除游标指向的前一个元素,而且一个next()函数之后,只能跟着最多一个remove()操作,多次调用remove()操作会报错。

当然去看源码的话,最为直接清楚。

直接只截取一段,迭代器全部的源码上面已经有贴过:

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;
            // next()函数将cursor游标后移一位,lastRet 置为当前元素下标
            return (E) elementData[lastRet = i];
        }

// 删除
public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                //删除后 lastRet置为-1
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
           

迭代器类新增了一个lastRet成员变量,用来记录游标指向的前一个元素。通过迭代器去删除这个元素的时候,可以更新迭代器中的游标和lastRet值,来保证不会因为删除元素而导致某个元素遍历不到。如果连续调用迭代器remove,会因为删除后没有调用next(),上个删除时lastRet重置,导致删除异常。

✨✨ 欢迎🔔订阅个人的微信公众号 享及时博文更新

设计模式-迭代器模式及应用

继续阅读