迭代器接口:
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());
}
使用方法:
ArrayList list=new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
迭代器通常被称为轻量级对象(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java 的 Iterator 只能单向移动。–摘自《java编程思想》
我们拿ArrayList举例,它实现了Collection接口中的Iterator iterator();方法
实现:
public Iterator<E> iterator() {
return new Itr();
}
new Itr()是ArrayList里面的内部类,该内部类实现了Iterator接口,重写了上面写的Iterator中的四个方法
事实上还有一个方法
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这个方法在next()方法一开始就被调用:
public E next() {
checkForComodification();
......
}
expectedModCount是预计修改次数,在我们一开始创建iterator的时候就会赋值,后面就不会改变
modCount 是修改次数
ArrayList中,当我们添加元素的时候,modCount++,这时候modCount != expectedModCount,checkForComodification抛出异常,这就是为什么,迭代器遍历的时候不能添加元素的原因。如果想添加元素,可以使用ListIterator(下面会提到)
iterator.next()方法可以理解为从起始位置 越过 第一个元素 来到到 第一个元素与第二个元素之间的位置,返回刚刚越过的元素的引用也就是AAA
remove方法会删除上次调用next方法时返回的元素,所以如果要想删除一个元素实现要越过它。
如果我们要删除BBB和CCC我们需要
Iterator iterator = list.iterator();
iterator.next();
iterator.next();//来到BBB和CCC中间
iterator.remove();//删除BBB
iterator.next();//来到CCC右边
iterator.remove();//删除CCC
使用iterator不需要知道它所遍历的序列的类型信息
ListIterator
有一点需要注意:ArrayList,LinkedList是有序集合,迭代器从头遍历到尾,这也就意味着,像add这种依赖位置的方法可以实现,并且依赖于迭代器来实现。而像Set,Map这样的无序集合就不能add,只能使用Iterator,为了让list实现迭代器的add,我们有提供了ListIterator。
ListIterator的add方法添加的元素位置在迭代器前面
ListIterator还可以从尾往头遍历
boolean hasPrevious();
E previous();
当从尾往头遍历时,remove()删除的是右边的元素(也是刚刚越过的元素)
add方法只依赖迭代器的位置,而remove方法不同,它依赖迭代器的状态
所以add方法可以连续调用多次,而remove方法不可以连续调用两次
另外ListIterator还有一个方法:
void set(E e);
是用一个新元素替换调用next或preivous方法返回的上一个元素
迭代器模式(23种设计模式之一):
- 迭代器模式属于行为模式
- 迭代器遍历这种方式不会暴露元素内部结构(比如有数组,有java集合类,或者其他什么方式),不需要知道你的类型也可以遍历
- 迭代器模式,提供统一遍历集合元素的接口,用一致的方式遍历集合元素
List中的迭代器
public Iterator<E> iterator() {
return new Itr();
}
返回的是一个Itr内部类,它实现了Iterator接口
private class Itr implements Iterator<E> {
int cursor; // 要返回的下一个元素的索引
int lastRet = -1; // 返回的最后一个元素的索引; 如果最后一个元素被删除了就返回-1(在remove中会被赋值为-1)
int expectedModCount = modCount; //判断是否更改list结构(通过list.add()添加,list.remove()删除,注意:list里面的修改set(index,value)不改变表结构)
Itr() {}
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;
//把当前元素(也就是刚刚越过去的元素)的位置赋值给lastRet
return (E) elementData[lastRet = i];
}
public void remove() {
//确保删除前已经next()了
if (lastRet < 0)
throw new IllegalStateException();
//迭代器remove前会做是否更改list结构的判断
checkForComodification();
try {
//迭代器中的remove也是使用list.remove()
ArrayList.this.remove(lastRet); //lastRet刚刚越过去元素的下标
cursor = lastRet;
//删除后就被赋值为-1,上面是if (lastRet < 0)
//这就是为什么迭代器不能连续删除元素的原因
//lastRet 也可以理解为迭代器的状态,-1为删除元素后的状态
lastRet = -1;
//这段代码改变了expectedModCount
//只许州官放火,不许百姓点灯🤷♂️
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
......
}
HashMap中的迭代器
要使用HashMap中的迭代器,有三种形式,或者说,我们要把hashmap先转化一下,然后再调用iterator()方法,有三种形式:1.KeySet;2.Values;3.EntrySet(是前面两种的合体)。
这是三个内部类,三个类中都有iterator()方法
这三个方法有很多相似性
KeySet的:
public final Iterator<K> iterator() { return new KeyIterator(); }
Values的:
public final Iterator<V> iterator() { return new ValueIterator(); }
EntrySet的:
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
这三个方法new的内部类在源码中都写在了一起整整齐齐😂(它们都继承了HashIterator)
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
final class ValueIterator extends HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
nextNode()方法就是HashIterator中的,我们就只看第三个EntryIterator 的nextNode(),前面两一样
下面是HashIterator的源码:
abstract class HashIterator {
Node<K,V> next; // 下一个节点
Node<K,V> current; // 当前节点
int expectedModCount; // 为防止迭代器遍历时期 修改结构所做的标记(map.put添加,其实这里还包括修改,map.remove删除)
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
//构造的时候就找到第一个元素
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
//如果链表中的当前节点还有next就直接返回,当前节点e
if ((next = (current = e).next) == null && (t = table) != null) {
//如果当前节点没有next,也就是说链表走到头了,进入do-while循环,进入下一个不为null的桶
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}