迭代器模式
迭代器模式是一种将集合的增删改操作与集合的顺序遍历操作分离的设计模式。集合只负责增删改操作,迭代器对于集合的内部类,专门负责顺序遍历。Java的Iterator是迭代器模式的经典实现。笔者jdk版本是11.0.4,不同版本的jdk的Iterator相关类及其实现有所不同,下面以jdk11.0.4为例。
Iterator
jdk定义了一个Iterator
接口,声明了hasNext
, next
, remove
方法,分别用于检查是否结束遍历、遍历下一个元素、删除元素的操作。
/**
* An iterator over a collection. {@code Iterator} takes the place of
* {@link Enumeration} in the Java Collections Framework. Iterators
* differ from enumerations in two ways:
*
* <ul>
* <li> Iterators allow the caller to remove elements from the
* underlying collection during the iteration with well-defined
* semantics.
* <li> Method names have been improved.
* </ul>
*
* <p>This interface is a member of the
* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
* Java Collections Framework</a>.
*
* @apiNote
* An {@link Enumeration} can be converted into an {@code Iterator} by
* using the {@link Enumeration#asIterator} method.
*
* @param <E> the type of elements returned by this iterator
*
* @author Josh Bloch
* @see Collection
* @see ListIterator
* @see Iterable
* @since 1.2
*/
public interface Iterator<E> {
/**
* Returns {@code true} if the iteration has more elements.
* (In other words, returns {@code true} if {@link #next} would
* return an element rather than throwing an exception.)
*
* @return {@code true} if the iteration has more elements
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws NoSuchElementException if the iteration has no more elements
*/
E next();
/**
* Removes from the underlying collection the last element returned
* by this iterator (optional operation). This method can be called
* only once per call to {@link #next}.
* <p>
* The behavior of an iterator is unspecified if the underlying collection
* is modified while the iteration is in progress in any way other than by
* calling this method, unless an overriding class has specified a
* concurrent modification policy.
* <p>
* The behavior of an iterator is unspecified if this method is called
* after a call to the {@link #forEachRemaining forEachRemaining} method.
*
* @implSpec
* The default implementation throws an instance of
* {@link UnsupportedOperationException} and performs no other action.
*
* @throws UnsupportedOperationException if the {@code remove}
* operation is not supported by this iterator
*
* @throws IllegalStateException if the {@code next} method has not
* yet been called, or the {@code remove} method has already
* been called after the last call to the {@code next}
* method
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}
/**
* Performs the given action for each remaining element until all elements
* have been processed or the action throws an exception. Actions are
* performed in the order of iteration, if that order is specified.
* Exceptions thrown by the action are relayed to the caller.
* <p>
* The behavior of an iterator is unspecified if the action modifies the
* collection in any way (even by calling the {@link #remove remove} method
* or other mutator methods of {@code Iterator} subtypes),
* unless an overriding class has specified a concurrent modification policy.
* <p>
* Subsequent behavior of an iterator is unspecified if the action throws an
* exception.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* while (hasNext())
* action.accept(next());
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
Iterable
Iterable
接口是可迭代对象的接口,其中iterator
方法返回一个迭代器对象。
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
集合类的基类接口Collection
中,实现了Iterable
接口。
public interface Collection<E> extends Iterable<E>
Itr
在jdk11.0.4中,List集合的抽象基类AbstractList
和具体的ArrayList
类中都各自实现了迭代器内部类Itr
, ListItr
.
以AbstractList.Itr
为例,属性cursor
是下一个元素的索引,lastRet
是最后一次返回的元素的索引,一般来说是cursor - 1
,expectedModCount
用于检查ConcurrentModification
异常(下面会详述)。可以看到,相比于大家很容易想到的实现,jdk对于next
和remove
的实现中只是多了对于ConcurrentModification
异常的检查checkForComodification
(下面会详述).
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
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() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
而ArrayList.Itr
只是对于AbstractList.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;
// prevent creating a synthetic constructor
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
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = ArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ListItr
ListItr
是对Itr
的功能扩展,提供了添加元素、反向遍历等功能。但是ListItr
只能用于AbstractList
的子类,而Itr
则可以用于所有Collection
的子类。下面是AbstractList.ListItr
的例子。
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public E previous() {
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor-1;
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
AbstractList.this.add(i, e);
lastRet = -1;
cursor = i + 1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
foreach遍历删除的ConcurrentModificationException及其解决
使用foreach风格的遍历的同时使用集合对象的remove
方法删除对象,可能会引发ConcurrentModificationException
异常,例如如下代码Main.java
会抛出该异常。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<>();
arr.add("1");
arr.add("2");
arr.add("12");
for (String num: arr) {
System.out.println(num.length());
if (num.length() == 2) {
arr.remove(num);
}
}
}
}
javac Main.java
,java Main
输出:
1
1
2
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at Main.main(Main.java:9)
使用jdk自带的反编译工具javap
查看foreach风格的遍历的实现(javap -v Main
),可以看到foreach遍历实际就是使用Iterator
进行遍历:42: invokeinterface #10, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
, 51: invokeinterface #11, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
public class Main
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #17 // Main
super_class: #18 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #18.#29 // java/lang/Object."<init>":()V
#2 = Class #30 // java/util/ArrayList
#3 = Methodref #2.#29 // java/util/ArrayList."<init>":()V
#4 = String #31 // 1
#5 = Methodref #2.#32 // java/util/ArrayList.add:(Ljava/lang/Object;)Z
#6 = String #33 // 2
#7 = String #34 // 12
#8 = String #35 // 3
#9 = Methodref #2.#36 // java/util/ArrayList.iterator:()Ljava/util/Iterator;
#10 = InterfaceMethodref #26.#37 // java/util/Iterator.hasNext:()Z
#11 = InterfaceMethodref #26.#38 // java/util/Iterator.next:()Ljava/lang/Object;
#12 = Class #39 // java/lang/String
#13 = Fieldref #40.#41 // java/lang/System.out:Ljava/io/PrintStream;
#14 = Methodref #12.#42 // java/lang/String.length:()I
#15 = Methodref #43.#44 // java/io/PrintStream.println:(I)V
#16 = Methodref #2.#45 // java/util/ArrayList.remove:(Ljava/lang/Object;)Z
#17 = Class #46 // Main
#18 = Class #47 // java/lang/Object
#19 = Utf8 <init>
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 main
#24 = Utf8 ([Ljava/lang/String;)V
#25 = Utf8 StackMapTable
#26 = Class #48 // java/util/Iterator
#27 = Utf8 SourceFile
#28 = Utf8 Main.java
#29 = NameAndType #19:#20 // "<init>":()V
#30 = Utf8 java/util/ArrayList
#31 = Utf8 1
#32 = NameAndType #49:#50 // add:(Ljava/lang/Object;)Z
#33 = Utf8 2
#34 = Utf8 12
#35 = Utf8 3
#36 = NameAndType #51:#52 // iterator:()Ljava/util/Iterator;
#37 = NameAndType #53:#54 // hasNext:()Z
#38 = NameAndType #55:#56 // next:()Ljava/lang/Object;
#39 = Utf8 java/lang/String
#40 = Class #57 // java/lang/System
#41 = NameAndType #58:#59 // out:Ljava/io/PrintStream;
#42 = NameAndType #60:#61 // length:()I
#43 = Class #62 // java/io/PrintStream
#44 = NameAndType #63:#64 // println:(I)V
#45 = NameAndType #65:#50 // remove:(Ljava/lang/Object;)Z
#46 = Utf8 Main
#47 = Utf8 java/lang/Object
#48 = Utf8 java/util/Iterator
#49 = Utf8 add
#50 = Utf8 (Ljava/lang/Object;)Z
#51 = Utf8 iterator
#52 = Utf8 ()Ljava/util/Iterator;
#53 = Utf8 hasNext
#54 = Utf8 ()Z
#55 = Utf8 next
#56 = Utf8 ()Ljava/lang/Object;
#57 = Utf8 java/lang/System
#58 = Utf8 out
#59 = Utf8 Ljava/io/PrintStream;
#60 = Utf8 length
#61 = Utf8 ()I
#62 = Utf8 java/io/PrintStream
#63 = Utf8 println
#64 = Utf8 (I)V
#65 = Utf8 remove
{
public Main();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String 1
11: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
14: pop
15: aload_1
16: ldc #6 // String 2
18: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: ldc #7 // String 12
25: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
28: pop
29: aload_1
30: ldc #8 // String 3
32: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
35: pop
36: aload_1
37: invokevirtual #9 // Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
40: astore_2
41: aload_2
42: invokeinterface #10, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
47: ifeq 87
50: aload_2
51: invokeinterface #11, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
56: checkcast #12 // class java/lang/String
59: astore_3
60: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
63: aload_3
64: invokevirtual #14 // Method java/lang/String.length:()I
67: invokevirtual #15 // Method java/io/PrintStream.println:(I)V
70: aload_3
71: invokevirtual #14 // Method java/lang/String.length:()I
74: iconst_2
75: if_icmpne 84
78: aload_1
79: aload_3
80: invokevirtual #16 // Method java/util/ArrayList.remove:(Ljava/lang/Object;)Z
83: pop
84: goto 41
87: return
LineNumberTable:
line 5: 0
line 6: 8
line 7: 15
line 8: 22
line 9: 29
line 10: 36
line 11: 60
line 12: 70
line 13: 78
line 15: 84
line 16: 87
StackMapTable: number_of_entries = 3
frame_type = 253 /* append */
offset_delta = 41
locals = [ class java/util/ArrayList, class java/util/Iterator ]
frame_type = 42 /* same */
frame_type = 250 /* chop */
offset_delta = 2
}
SourceFile: "Main.java"
如果不使用foreach风格的遍历,直接使用Iterator
遍历,并使用Iterator
的remove
方法进行删除,则不会引发异常,程序可以正常工作。
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<>();
arr.add("1");
arr.add("2");
arr.add("12");
// for (String num: arr) {
// System.out.println(num.length());
// if (num.length() == 2) {
// arr.remove(num);
// }
// }
Iterator<String> iter = arr.iterator();
while (iter.hasNext()) {
String num = iter.next();
System.out.println(num.length());
if (num.length() == 2) {
iter.remove();
}
}
}
}
为什么使用集合类的remove
方法会抛ConcurrentModificationException
异常而Iterator
的remove
方法不会呢?这就需要再次回到jdk源码中。ArrayList.Itr.remove()
方法:
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();
}
}
ArrayList.remove(Object)
方法:
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* {@code Objects.equals(o, get(i))}
* (if such an element exists). Returns {@code true} if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
/**
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
相比于ArrayList
的remove(Object)
方法,Itr
的remove()
方法会检查Itr
类的expectedModCount
属性与外部List类的modCount
属性是否相等(checkForComodification();
),并在删除元素后保证expectedModCount
与modCount
相等(expectedModCount = modCount;
)。这样就解决了ConcurrentModificationException
异常问题。
- 在
Itr
类的checkForComodification
方法中,检查Itr
类的expectedModCount
属性与外部List类的modCount
属性是否相等,从而决定是否抛出ConcurrentModificationException
异常:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
来源:CSDN
作者:da_kao_la
链接:https://blog.csdn.net/da_kao_la/article/details/104220046