容器中的Iterator设计模式
1 Iterator设计模式
由于ArrayList
和LinkedList
这两种list
是我们工作中最常用到的List
容器。当然数组和链表也是两种常见的基本数据结构,其他基本数据结构还有堆栈、队列、树等,对java
容器的学习,也可以看做是对数据结构的学习和使用。
在ArrayList
和LinkedList
的分析中,都没有对容器遍历进行分析,前面说过迭代器相对独立和复杂,而且它又是一种非常经典的设计模式(说经典不过使用场景也就这一个...),这里开始对Iterator
进行分析。
1.1统一接口
先想一个问题,现在有ArrayList
和LinkedList
两种容器,我们怎么实现容器的可替换性
呢?什么叫做可替换性,就是底层的容器实现可以随意改变,而对用户的使用不产生任何影响,说到这里应该都明白了就是要统一接口,用户只需要面向接口编程,来看看ArrayList
和LinkedList
是不是这样做的
- ArrayList的定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- LinkedList的定义:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
从定义上可以看到ArrayList
和LinkedList
都实现了List
接口,List
接口的定义:
public interface List<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll( int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
E get( int index);
E set( int index, E element);
void add( int index, E element);
E remove( int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator( int index);
List<E> subList( int fromIndex, int toIndex);
}
可以看到List
中对容器的各种操作add、remove、set、get、size
等进行了统一定义,同时List
实现了Collection
接口,继续看下Collection
接口的定义(先不关心Iterator
):
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
}
有了这两个接口,对于ArrayList
和LinkeList
的操作是不是就可以这么写了呢?
Collection<String> collection = new ArrayList<String>();
collection.add("hello");
collection.add("java");
collection.remove("hello");
对于ArrayList
的实现不满意,那么换成LinkedList
实现,
Collection<String> collection = new LinkedList<String>();
collection.add("hello");
collection.add("java");
collection.remove("hello");
对于用户来说,add、remove
等操作是没有任何影响的,好了,到这里了解了统一接口,面向接口编程的好处,接下来在思考另外一个问题,怎么给容器提供一种遍历方式。
1.2 Iterator设计思想
可能会认为不就是遍历嘛,很好提供啊,ArrayList
就用数组下标进行遍历,LinkedList
就用指针next
进行遍历,仔细想一下真的这么简单嘛?结合上一个问题,如果底层的容器实现变化了,用户的使用是不是也需要根据具体实现的数据结构来改变遍历方式呢?显然这样是不科学的
是的,需要统一一种遍历方式,提供一个统计遍历接口,而具体实现需要具体的容器自己根据自身数据结构特点来完成,而对于用户就只有一个接口而已,万年不变。于是就有了Iterator
,接下来看下Iterator
的实现
先回过头去看Collection
的定义,发现Collection
实现了一个Iterable
接口,来看下的Iterable
的定义,
/** Implementing this interface allows an object to be the target of
* the "foreach" statement.
* @since 1.5
*/
public interface Iterable<T> {
/** * Returns an iterator over a set of elements of type T.
*
* @return an Iterator.
*/
Iterator<T> iterator();
}
英文注释说,实现iterable
接口的类可以使用foreach
操作,然后并要求实现Iterator<T> iterator()
方法,该方法返回一个Iterator
接口对象,来看下Iterator
接口,
public interface Iterator<E> {
// 是否还有元素
boolean hasNext();
// 下一个元素
E next();
// 将迭代器返回的元素删除
void remove();
}
Iterator
接口一共有三个方法,通过这三个方法就可以实现通用遍历了
Collection<String> collection = new ArrayList<String>();
collection.add("hello");
collection.add("java");
Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()) {
System. out.println(iterator.next());
}
用户再也不用关心容器的底层实现了,不管你是数组还是链表还是其他什么,只需要用Iterator
就可以进行遍历了。
到这里Iterator
的设计原则和思路实际上已经讲完了,我们来整理下Iterator
模式的几个角色:
- 迭代器
Iterator
接口。 - 迭代器
Iterator
的实现类,要求重写hasNext(),next(),remove()
三个方法 。 - 容器统一接口,要求有一个返回
Iterator
接口的方法。 - 容器实现类,实现一个返回
Iterator
接口的方法。
1.3 自己实现容器的Iterator
遍历
如果让我们自己来实现Iterator
模式,应该怎么来实现呢,试一下。
Iterator接口:
public interface MyIterator {
Object next();
boolean hasNext();
}
容器统一接口:
public interface MyCollection {
void add(Object o);
int size();
MyIterator iterator();
}
容器实现类和Iterator
实现类(Iterator
实现类为容器内部类):
public class MyArrayList implements MyCollection {
private Object[] data ;
private int size;
public MyArrayList() {
data = new Object[10];
}
public void add(Object o) {
if (size == data. length) {
Object[] newData = new Object[data .length * 2];
System. arraycopy(data, 0, newData, 0, data.length );
data = newData;
}
data[size ] = o;
size++;
}
public int size() {
return size ;
}
@Override
public MyIterator iterator() {
return new Itr();
}
private class Itr implements MyIterator {
private int index = 0;
@Override
public boolean hasNext() {
if (index >= size) {
return false;
} else {
return true;
}
}
@Override
public Object next() {
Object o = data[index ];
index++;
return o;
}
}
}
应用一下:
public class Test {
public static void main(String[] args) {
MyCollection c = new MyArrayList();
c.add( "t");
c.add( "s");
c.add( "t");
c.add( "d");
System. out.println(c.size());
MyIterator itr = c.iterator();
while (itr.hasNext()) {
System. out.println(itr.next());
}
}
}
看看结果:
4
t
s
t
d
没问题,很容易就实现了对不对,当然这里只是简单模拟,没有过多的追求效率和优雅的设计。
1.4 ArrayList的Iterator实现
下面来看一看ArrayList
和LinkedList
对Iterator
接口的实现吧,ArrayList
的Iterator
实现封装在AbstractList
中,看一下它的实现:
public Iterator<E> iterator() {
return new Itr();
}
和自己实现的一样,采用内部类实现 Iterator
接口。
private class Itr implements Iterator<E> {
// 将要next返回元素的索引
int cursor = 0;
// 当前返回的元素的索引,初始值-1 5 int lastRet = -1;
/** * The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
// 由于cursor是将要返回元素的索引,也就是下一个元素的索引,和size比较是否相等,也就是判断是否已经next到最后一个元素
return cursor != size();
}
public E next() {
checkForComodification();
try {
// 根据下一个元素索引取出对应元素
E next = get( cursor);
// 更新lastRet为当前元素的索引,cursor加1
lastRet = cursor ++;
// 返回元素
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
// remove前必须先next一下,先取得当前元素
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList. this.remove(lastRet );
// 确保lastRet比cursor小、理论上永远lastRet比cursor小1
if (lastRet < cursor)
// 由于删除了一个元素cursor回退1
cursor--;
// 重置为-1
lastRet = -1;
expectedModCount = modCount ;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList
的Iterator
实现起来和我们自己实现的大同小异,很好理解。
有一点需要指出的是这里的checkForComodification
检查,modCount
这个变量到底是做什么的,这里简单解释一下:
迭代器Iterator
允许在容器遍历的时候对元素进行删除,但是有一点问题那就是当多线程操作容器的时候,在用Iterator
对容器遍历的同时,其他线程可能已经改变了该容器的内容(add、remove
等操作),所以每次对容器内容的更改操作(add、remove
等)都会对modCount+1
,这样相当于乐观锁的版本号+1
,当在Iterator
遍历中remove
时,检查到modCount
发生了变化,则马上抛出异常ConcurrentModificationException
,这就是Java容器
中的fail-fast
机制。
1.5 LinkedList的Iterator实现
LinkedList
的Iterator
实现定义在AbstracSequentialtList
中,实现在AbstractList
中,看一下:
AbstracSequentialtList
的定义:
public Iterator<E> iterator() {
return listIterator();
}
AbstractList
的实现:
public ListIterator<E> listIterator() {
return listIterator(0);
}
public ListIterator<E> listIterator(final int index) {
if (index<0 || index>size())
throw new IndexOutOfBoundsException( "Index: "+index);
return new ListItr(index);
}
看一下ListItr
实现:
private class ListItr implements ListIterator<E> {
// 最后一次返回的节点,默认位header节点
private Entry<E> lastReturned = header;
// 将要返回的节点
private Entry<E> next ;
// 将要返回的节点index索引
private int nextIndex;
private int expectedModCount = modCount;
ListItr( int index) {
// 索引越界检查
if (index < 0 || index > size)
throw new IndexOutOfBoundsException( "Index: "+index+ ", Size: "+size );
// 简单二分,判断遍历的方向
if (index < (size >> 1)) {
// 取得index位置对应的节点
next = header .next;
for (nextIndex =0; nextIndex<index; nextIndex++)
next = next .next;
} else {
next = header ;
for (nextIndex =size; nextIndex>index; nextIndex --)
next = next .previous;
}
}
public boolean hasNext() {
// 根据下一个节点index是否等于size,判断是否有下一个节点
return nextIndex != size;
}
public E next() {
checkForComodification();
// 遍历完成
if (nextIndex == size)
throw new NoSuchElementException();
// 赋值最近一次返回的节点
lastReturned = next ;
// 赋值下一次要返回的节点(next后移)
next = next .next;
// 将要返回的节点index索引+1
nextIndex++;
return lastReturned .element;
}
public boolean hasPrevious() {
return nextIndex != 0;
}
// 返回上一个节点(双向循环链表嘛、可以两个方向遍历)
public E previous() {
if (nextIndex == 0)
throw new NoSuchElementException();
lastReturned = next = next. previous;
nextIndex--;
checkForComodification();
return lastReturned .element;
}
public int nextIndex() {
return nextIndex ;
}
public int previousIndex() {
return nextIndex -1;
}
public void remove() {
checkForComodification();
// 取出当前返回节点的下一个节点
Entry<E> lastNext = lastReturned.next ;
try {
LinkedList. this.remove(lastReturned );
} catch (NoSuchElementException e) {
throw new IllegalStateException();
}
// 确认下次要返回的节点不是当前节点,如果是则修正
if (next ==lastReturned)
next = lastNext;
else
// 由于删除了一个节点,下次要返回的节点索引-1
nextIndex--;
// 重置lastReturned为header节点 87 lastReturned = header ;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == header)
throw new IllegalStateException();
checkForComodification();
lastReturned.element = e;
}
public void add(E e) {
checkForComodification();
lastReturned = header ;
addBefore(e, next);
nextIndex++;
expectedModCount++;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
LinkedList
的Iterator
实现就分析完了,从这里可以看出数组和链表的遍历方式不同,但对于用户统一使用Iterator
迭代器进行遍历器,做到只需面对接口编程,这也是Iterator
的最终要做到的。
Iterator
是比较简单的一种设计模式,使用场景也仅限于此,不要纠结于Iterator
的应用而是要明白其设计思想,同时要对一些常用数据结构应该要了解掌握