在设计模式系列之 迭代器模式中介绍了迭代器模式的基本知识,该模式主要包含了抽象迭代器、具体迭代器、抽象聚合类、具体聚合类。前两个类规范了对聚合类的遍历方式,后两个类主要用于存储数据和提供对应的迭代器。java集合框架涉及的众多类,譬如ArrayList、HashMap等,都可称为用于存储数据的聚合类,并且java的作者也为其提供了对应迭代器。
随着java语言的版本更迭,其集合框架使用迭代器模式的方式同样发生了变化。本文基于不同版本JDK,分别介绍迭代器模式在java集合框架(除去Map相关接口)中应用。
JDK1.2中的迭代器
在该版本的集合框架中,尚未出现Iterable接口。CoLlection接口是所有集合类的基类。分别截取部分Collection接口、Iterator接口、ArrayList类、Itr类(ArrayList私有内部类)的部分源码,可以发现Collection接口、Iterator接口、ArrayList类均出自jdk1.2。
-
部分Collection接口源码。注意此时JDK中没有Iterable类
/* * @since 1.2 */ public interface Collection<E>{ //定义返回迭代器的方法 Iterator<E> iterator(); }
-
部分Iterator接口源码
/** * @since 1.2 */ public interface Iterator<E> { boolean hasNext(); E next(); }
-
部分ArrayList类源码
/** * @since 1.2 */ public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public Iterator<E> iterator() { return new Itr(); } }
-
部分Itr类源码
//ArrayList的内部私有类 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]; } }
参照迭代器模式,此时的
- Collection接口相当于迭代器模式中的抽象聚合类,该聚合类中定义了一个返回迭代器的方法;
- Iterator接口相当于迭代器模式中的抽象迭代器类,定义了实现迭代的关键方法;
- ArrayList类相当于具体的聚合类,返回了一个具体迭代器Itr实例;
- Itr类相当于具体的迭代器类,实现了迭代逻辑;
同迭代器模式不同的是,具体的迭代器类Itr不是一个独立的类,而是作为ArrayList类的私有内部类,但Itr类所处的位置不影响迭代器模式发挥其存储与遍历分离的解耦优势。
JDK1.5之后的迭代器
在jdk1.5中,新增了Iterable接口,并且Collection继承了该接口,因此Iterable接口成为集合框架的最上层基类。该接口的接口描述介绍了其主要作用“实现该接口的类可以使用foreach循环”。
-
部分Iterable接口源码
/** * Implementing this interface allows an object to be the target of * the "for-each loop" statement. (实现该接口的类可以使用foreach循环) * @since 1.5 */ public interface Iterable<T> { /** * @return an Iterator. */ Iterator<T> iterator(); /** * @param action The action to be performed for each element * @throws NullPointerException if the specified action is null * @since 1.8 */ default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } /** * @return a {@code Spliterator} over the elements described by this * {@code Iterable}. * @since 1.8 */ default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } }
-
部分Colleciton接口源码
/** * @since 1.2 */ public interface Collection<E> extends Iterable<E> { Iterator<T> iterator(); }
虽然Collection接口继承了Iterable接口,并且Iterable中只有三个方法,但这并不影响以Iterable接口为基类的集合框架仍旧符合迭代器模式,此时Iterable接口可以认为是迭代器模式中抽象聚合类。其他三个角色同jdk1.2中基本一致。
Iterable接口和Iterator接口的区别
Itearable接口的源码注释交待了该类的主要作用,即新增该接口是为了达到”实现该接口的类可以使用foreach语句“的目的,强调了实现Iterable接口和使用foreach语法遍历的因果关系,与Iterator接口无关。Iterator接口是基于迭代器模式,实现了对聚合类的存储和遍历的分离。两者的拼写类似,但各自强调的内容不同。
- iterator接口强调其实现类可使用迭代器遍历
- iterable接口强调其实现类不仅可以使用迭代器遍历,也可以使用foreach语法遍历。
譬如,对于Collection的引用对象,在jdk1.5之前只有一种遍历方式,只能使用对应的迭代器访问其元素。而在jdk1.5之后,即继承了Iterable接口之后,有两种遍历方式,一种使用迭代器,一种使用foreach语法。
对网上关于Collection接口为什么不直接继承Iterator接口?的讨论,可以站在迭代器模式的角度回答,Collection接口属于抽象聚合角色,Iterator接口属于抽象迭代器角色,为实现存储与遍历分离,两者本就不属于同一继承链,只是存在依赖关系。即使在java1.5版本中Collection接口继承了Iterable接口,Iterable接口和Iterator仍是只存在依赖关系【依赖关系指在迭代器模式中,Iterable作为抽象聚合角色依赖抽象迭代器角色Iterator】,Iterable接口和Iterator接口只是拼写和含义相近而已。假设Collection接口继承Iterator接口,此时已不再符合迭代器模式的模式结构,也就无法达到迭代器模式的优点。
延申
- 快速失败
代码原因:在foreach中使用集合类(arraylist等)的remove/add方法
现象:抛出Concurrent Modification Exception
底层原因:modCount!=expectedmodCount
,集合类在执行remove方法时,只维护modCount,不维护expecedCount,造成两个数不相等。
正确操作:使用迭代器(arraylist的itr迭代器等)的remove方法