Java ArrayList 的迭代器使用
Java ArrayList 的迭代器基础概念
在Java编程中,ArrayList
是一种常用的动态数组实现,它允许我们灵活地存储和管理一组对象。而迭代器(Iterator)则是一种用于遍历集合(如 ArrayList
)中元素的工具。迭代器提供了一种统一的方式来访问集合中的元素,而无需关心集合的内部实现细节。
迭代器模式的核心思想是将集合对象的遍历操作从集合类中分离出来,封装到一个独立的迭代器对象中。这样,集合类只需要负责存储和管理元素,而迭代器对象则负责遍历这些元素。这种分离使得代码更加模块化和可维护,同时也方便了不同类型集合的遍历操作。
在Java中,ArrayList
实现了 List
接口,而 List
接口继承自 Collection
接口。Collection
接口定义了 iterator()
方法,该方法返回一个 Iterator
对象,用于遍历集合中的元素。Iterator
接口定义了三个主要方法:hasNext()
、next()
和 remove()
。
hasNext()
方法:用于判断集合中是否还有下一个元素。如果有,则返回true
;否则,返回false
。next()
方法:返回集合中的下一个元素,并将迭代器的位置向前移动一位。如果没有下一个元素,则抛出NoSuchElementException
异常。remove()
方法:用于从集合中移除当前迭代器所指向的元素。该方法只能在调用next()
方法之后调用,并且只能调用一次。如果在没有调用next()
方法之前调用remove()
方法,或者在调用next()
方法之后多次调用remove()
方法,则会抛出IllegalStateException
异常。
使用迭代器遍历 ArrayList
下面通过一个简单的示例代码来演示如何使用迭代器遍历 ArrayList
:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListIteratorExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 获取迭代器
Iterator<String> iterator = list.iterator();
// 使用迭代器遍历 ArrayList
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
}
}
在上述代码中,首先创建了一个 ArrayList
并添加了三个字符串元素。然后通过调用 list.iterator()
方法获取一个 Iterator
对象。接着,使用 while
循环和 iterator.hasNext()
方法来判断是否还有下一个元素,如果有,则通过 iterator.next()
方法获取下一个元素并打印出来。
这种遍历方式与使用传统的 for
循环遍历 ArrayList
有所不同。使用迭代器遍历的好处在于,它提供了一种统一的方式来遍历不同类型的集合,而无需关心集合的具体实现。此外,迭代器还支持在遍历过程中删除元素,这在某些场景下非常有用。
在迭代过程中删除元素
前面提到,Iterator
接口提供了 remove()
方法,允许我们在遍历集合的过程中删除当前迭代器所指向的元素。下面是一个示例代码,展示了如何在迭代 ArrayList
时删除元素:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListIteratorRemoveExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 获取迭代器
Iterator<String> iterator = list.iterator();
// 使用迭代器遍历并删除元素
while (iterator.hasNext()) {
String element = iterator.next();
if ("Banana".equals(element)) {
iterator.remove();
}
}
// 打印删除元素后的 ArrayList
System.out.println(list);
}
}
在上述代码中,在遍历 ArrayList
的过程中,通过判断当前元素是否为 "Banana",如果是,则调用 iterator.remove()
方法将其从集合中删除。最后打印删除元素后的 ArrayList
,可以看到 "Banana" 已经被成功删除。
需要注意的是,如果在遍历集合时使用 ArrayList
的 remove()
方法直接删除元素,而不是使用迭代器的 remove()
方法,可能会导致 ConcurrentModificationException
异常。这是因为在使用迭代器遍历集合时,迭代器会维护一个内部的版本号,当集合结构发生变化(如调用 ArrayList
的 remove()
方法)时,版本号会发生改变,而迭代器在调用 next()
或 remove()
方法时会检查版本号是否一致,如果不一致,则抛出 ConcurrentModificationException
异常。
增强型 for 循环与迭代器
在Java 5.0 引入了增强型 for 循环(也称为 for - each 循环),它为遍历集合提供了一种更简洁的语法。增强型 for 循环实际上是基于迭代器实现的,编译器会将增强型 for 循环转换为使用迭代器的代码。
下面是一个使用增强型 for 循环遍历 ArrayList
的示例:
import java.util.ArrayList;
import java.util.List;
public class ArrayListForEachExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 使用增强型 for 循环遍历 ArrayList
for (String element : list) {
System.out.println(element);
}
}
}
在上述代码中,使用增强型 for 循环遍历 ArrayList
,代码更加简洁明了。但是需要注意的是,增强型 for 循环不支持在遍历过程中删除元素。如果需要在遍历过程中删除元素,仍然需要使用迭代器的 remove()
方法。
例如,如果尝试在增强型 for 循环中使用 ArrayList
的 remove()
方法删除元素,会导致 ConcurrentModificationException
异常:
import java.util.ArrayList;
import java.util.List;
public class ArrayListForEachRemoveErrorExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 尝试在增强型 for 循环中删除元素,会抛出 ConcurrentModificationException
for (String element : list) {
if ("Banana".equals(element)) {
list.remove(element);
}
}
}
}
为了在遍历过程中安全地删除元素,应使用迭代器:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListIteratorRemoveSafeExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 使用迭代器遍历并删除元素
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if ("Banana".equals(element)) {
iterator.remove();
}
}
// 打印删除元素后的 ArrayList
System.out.println(list);
}
}
双向迭代器 ListIterator
除了 Iterator
接口,Java 还提供了 ListIterator
接口,它继承自 Iterator
接口,专门用于遍历 List
类型的集合,如 ArrayList
。ListIterator
接口提供了一些额外的方法,使得在遍历 List
时更加灵活。
ListIterator
接口新增的主要方法有:
hasPrevious()
方法:用于判断是否还有前一个元素。如果有,则返回true
;否则,返回false
。previous()
方法:返回前一个元素,并将迭代器的位置向后移动一位。如果没有前一个元素,则抛出NoSuchElementException
异常。nextIndex()
方法:返回下一个元素的索引。previousIndex()
方法:返回前一个元素的索引。add(E e)
方法:在当前位置插入指定元素。set(E e)
方法:用指定元素替换上次调用next()
或previous()
方法返回的元素。
下面是一个使用 ListIterator
遍历 ArrayList
的示例代码,展示了如何使用这些新增方法:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ArrayListListIteratorExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 获取 ListIterator
ListIterator<String> listIterator = list.listIterator();
// 正向遍历并打印元素
System.out.println("正向遍历:");
while (listIterator.hasNext()) {
String element = listIterator.next();
System.out.println(element);
}
// 反向遍历并打印元素
System.out.println("反向遍历:");
while (listIterator.hasPrevious()) {
String element = listIterator.previous();
System.out.println(element);
}
// 在指定位置插入元素
listIterator = list.listIterator(1);
listIterator.add("Date");
// 打印插入元素后的 ArrayList
System.out.println("插入元素后:");
for (String element : list) {
System.out.println(element);
}
// 修改指定元素
listIterator = list.listIterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
if ("Date".equals(element)) {
listIterator.set("Fig");
}
}
// 打印修改元素后的 ArrayList
System.out.println("修改元素后:");
for (String element : list) {
System.out.println(element);
}
}
}
在上述代码中,首先获取了一个 ListIterator
对象,然后分别进行了正向遍历和反向遍历。接着,使用 add()
方法在指定位置插入了一个元素,最后使用 set()
方法修改了指定元素。通过这些操作,可以看到 ListIterator
提供了比 Iterator
更丰富的功能,适用于需要在遍历 List
时进行更多操作的场景。
迭代器的内部实现原理
了解迭代器的内部实现原理有助于我们更好地理解其工作机制和性能特点。在 ArrayList
中,迭代器的实现是基于数组的索引。
当调用 ArrayList
的 iterator()
方法时,会创建一个 Itr
内部类的实例,该类实现了 Iterator
接口。Itr
类维护了一个 cursor
变量,用于记录当前迭代的位置,初始值为 0。同时,还维护了一个 lastRet
变量,用于记录上一次调用 next()
方法返回的元素的索引,初始值为 -1。
hasNext()
方法的实现很简单,只需判断 cursor
是否小于 ArrayList
的大小即可:
public boolean hasNext() {
return cursor != size;
}
next()
方法会先检查 cursor
是否超过了 ArrayList
的大小,如果超过则抛出 NoSuchElementException
异常。然后,返回当前位置的元素,并将 cursor
加 1,同时更新 lastRet
为当前位置:
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];
}
remove()
方法则会检查 lastRet
是否为 -1,如果是,则抛出 IllegalStateException
异常。然后,调用 ArrayList
的 remove()
方法删除指定索引的元素,并更新 cursor
和 lastRet
:
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();
}
}
其中,checkForComodification()
方法用于检查 ArrayList
的结构是否发生变化。在 ArrayList
中,每次对集合结构进行修改(如添加、删除元素)时,modCount
变量会增加。迭代器在调用 next()
或 remove()
方法时,会检查 expectedModCount
是否与 modCount
相等,如果不相等,则抛出 ConcurrentModificationException
异常,以保证在遍历过程中集合结构的一致性。
ListIterator
的实现类似,只不过它是由 ListItr
内部类实现,该类继承自 Itr
类,并实现了 ListIterator
接口。ListItr
类增加了对反向遍历、插入和修改元素等功能的支持。
迭代器的性能考虑
在使用迭代器遍历 ArrayList
时,性能是一个需要考虑的因素。一般来说,使用迭代器遍历 ArrayList
的性能与使用传统的 for
循环遍历的性能相近。
使用迭代器遍历的优势在于其代码的简洁性和通用性,特别是在处理不同类型的集合时。然而,由于迭代器内部维护了一些状态变量(如 cursor
和 lastRet
),并且每次调用 next()
方法时都需要进行边界检查和 modCount
检查,因此在某些极端情况下,可能会比直接使用 for
循环略慢。
例如,在对一个非常大的 ArrayList
进行简单的顺序遍历,并且不需要在遍历过程中删除元素时,使用传统的 for
循环可能会有更好的性能:
import java.util.ArrayList;
import java.util.List;
public class ArrayListPerformanceComparison {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
list.add(i);
}
long startTime = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
Integer element = list.get(i);
}
long endTime = System.currentTimeMillis();
System.out.println("使用 for 循环遍历时间: " + (endTime - startTime) + " 毫秒");
startTime = System.currentTimeMillis();
for (Integer element : list) {
}
endTime = System.currentTimeMillis();
System.out.println("使用增强型 for 循环遍历时间: " + (endTime - startTime) + " 毫秒");
}
}
在上述代码中,分别使用传统的 for
循环和增强型 for 循环(基于迭代器)对一个包含 100 万个元素的 ArrayList
进行遍历,并比较它们的执行时间。在实际测试中,可能会发现传统的 for
循环在这种情况下性能略好一些。
但是,当需要在遍历过程中删除元素,或者处理不同类型的集合时,使用迭代器的优势就会体现出来。而且,现代的Java编译器和虚拟机对迭代器的实现进行了优化,在大多数实际应用场景中,迭代器的性能与传统 for
循环的性能差异并不明显。
并发环境下的迭代器使用
在多线程并发访问 ArrayList
时,使用迭代器需要特别小心。由于 ArrayList
本身不是线程安全的,在多线程环境下同时对 ArrayList
进行遍历和修改操作,很容易导致 ConcurrentModificationException
异常。
例如,假设有两个线程,一个线程在遍历 ArrayList
,另一个线程在向 ArrayList
中添加元素:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListConcurrentModificationExample {
private static List<String> list = new ArrayList<>();
public static void main(String[] args) {
list.add("Apple");
list.add("Banana");
Thread thread1 = new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("线程1遍历元素: " + element);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("Cherry");
System.out.println("线程2添加元素: Cherry");
});
thread1.start();
thread2.start();
}
}
在上述代码中,thread1
线程在遍历 ArrayList
,thread2
线程在 thread1
遍历过程中向 ArrayList
中添加了一个元素。运行这段代码,很可能会抛出 ConcurrentModificationException
异常。
为了在并发环境下安全地使用迭代器遍历 ArrayList
,可以采取以下几种方法:
使用线程安全的集合类
Java 提供了一些线程安全的集合类,如 CopyOnWriteArrayList
。CopyOnWriteArrayList
在进行写操作(如添加、删除元素)时,会创建一个新的数组,并将原数组的内容复制到新数组中,然后在新数组上进行操作。而读操作(如遍历)则直接使用原数组,这样就避免了在遍历过程中数组结构的变化,从而避免了 ConcurrentModificationException
异常。
下面是使用 CopyOnWriteArrayList
的示例:
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) {
list.add("Apple");
list.add("Banana");
Thread thread1 = new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("线程1遍历元素: " + element);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("Cherry");
System.out.println("线程2添加元素: Cherry");
});
thread1.start();
thread2.start();
}
}
在上述代码中,使用 CopyOnWriteArrayList
代替了 ArrayList
,这样在多线程环境下遍历和修改集合时就不会抛出 ConcurrentModificationException
异常。不过,需要注意的是,CopyOnWriteArrayList
的写操作性能相对较低,因为每次写操作都需要复制数组,适用于读多写少的场景。
使用同步机制
另一种方法是使用同步机制,如 synchronized
关键字或 ReentrantLock
,来保证在同一时间只有一个线程可以访问 ArrayList
。
下面是使用 synchronized
关键字的示例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListSynchronizedExample {
private static List<String> list = new ArrayList<>();
public static void main(String[] args) {
list.add("Apple");
list.add("Banana");
Thread thread1 = new Thread(() -> {
synchronized (list) {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("线程1遍历元素: " + element);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (list) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("Cherry");
System.out.println("线程2添加元素: Cherry");
}
});
thread1.start();
thread2.start();
}
}
在上述代码中,通过 synchronized
关键字对 list
对象进行同步,确保在同一时间只有一个线程可以访问 ArrayList
,从而避免了 ConcurrentModificationException
异常。这种方法虽然可以保证线程安全,但在高并发情况下,由于线程之间的竞争锁,可能会导致性能下降。
总结
迭代器是Java中遍历集合(如 ArrayList
)的重要工具,它提供了一种统一、灵活且方便的方式来访问集合中的元素。通过本文的介绍,我们深入了解了 ArrayList
迭代器的基本概念、使用方法、内部实现原理、性能考虑以及在并发环境下的使用。
在实际编程中,应根据具体的需求选择合适的遍历方式。如果只需要简单地顺序遍历集合,并且不需要在遍历过程中删除元素,传统的 for
循环或增强型 for 循环可能是不错的选择;如果需要在遍历过程中删除元素,或者需要处理不同类型的集合,使用迭代器则更为合适。在并发环境下,要注意选择合适的线程安全策略,以避免 ConcurrentModificationException
异常的发生。
希望通过本文的讲解,能帮助读者更好地掌握 Java ArrayList
迭代器的使用,写出更加健壮、高效的Java代码。