在程序设计中,要访问集合对象时。一般通过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重置,导致删除异常。
✨✨ 欢迎🔔订阅个人的微信公众号 享及时博文更新