- 1. 集合的迭代操作
- 2. Iterable 接口和 Iterator 迭代器
- 3. List 接口 和 ListIterator 迭代器
- 4. Enumeration 接口
- 5. 深入分析 for-each 和迭代删除操作
1. 集合的迭代操作
- 集合的迭代, 就是把集合中的元素一个个遍历出来.
- 集合的迭代有四种选择:
for
循环/增强for
循环Iterator
迭代器ListIterator
迭代器Enumeration
迭代器(已淘汰)
1.1. 迭代操作实例
- 假设如下有一个列表, 要对它进行遍历.
List list = new ArrayList(); list.add("A"); list.add("B"); list.add("C"); list.add("D");
//方式1: for 循环 for (int index = 0; index < list.size(); index ++){ System.out.println(list.get(index)); }
//方式1: 增强 for 循环 for (Object ele : list){ System.out.println(ele); }
//方式2: 通过 while 使用迭代器 Iterator iterator() Iterator it = list.iterator();//获取集合的迭代器 while (it.hasNext()){ System.out.println(it.next()); }
//方式2: 通过 for 使用迭代器 Iterator iterator() Iterator it = list.iterator();//获取集合的迭代器 for (Iterator it = list.iterator(); it.hasNext();){ //for 循环结束后会释放迭代器占用的资源, 效能会更好一点 System.out.println(it.next()); }
//方式3: ListIterator 正向遍历使用方式和 Iterator 一致, 不再举例 //PASS...
//方式4: 已淘汰, 在接口介绍中有实例.
## 2. Iterable 接口和 Iterator 迭代器
- `Iterator` 为 Java 中的迭代器, 是能够对集合进行迭代遍历的底层依赖.
- 而 `Iterable` 接口里定义了返回 `Iterator` 对象的方法, 相当于对 `Iterator` 的封装,
因此实现了 `Iterable` 接口的类可以支持 `for-each` 循环.
- `Collection` 接口又是 `Iterable` 的子接口, 也接收了返回 `Iterator` 对象的方法,
因此所有 `Collection` 集合的实现类都能用调用迭代器对象进行集合遍历.
### 2.1. Iterator 接口中定义的对象方法
#### 2.1.1. hasNext() 方法
- 定义:
`boolean hasNext()`
- 作用:
如果仍有元素可以迭代, 则返回 `true`.
#### 2.1.2. next() 方法
- 定义:
`E next()`
- 作用:
返回迭代的下一个元素.
#### 2.1.3. remove() 方法
- 定义:
`void remove()`
- 作用:
从迭代器指向的 `collection` 中移除迭代器返回的最后一个元素(可选操作).
- 注意:
每次调用 `next()` 只能调用一次此方法. 如果进行迭代时用调用此方法之外的
其他方式修改了该迭代器所指向的 `collection`, 则迭代器的行为是不确定的.
#### 2.1.4. 关于迭代器指针的指向
- 最开始迭代器的指针是指向第一个元素之前的位置.
- 调用 `next()` 方法之后指针就向下移动一位.
- 迭代器所指位置是根据调用 `next()` 方法次数决定的.
- 因此 `hasNext()` 方法指向是和迭代器指向是一致的.
## 3. List 接口 和 ListIterator 迭代器
- `List` 接口提供了特殊的迭代器, 称为 `ListIterator`. 这是 `Iterator` 的子接口.
- 该迭代器除了允许 `Iterator` 接口提供的正常操作外, 还允许元素插入和替换,
以及双向访问, 还提供了一个方法来获取从列表中指定位置开始的列表迭代器.
### 3.1. ListIterator 接口中定义的一些方法
- 这里只列举向前迭代的方法, 该接口比较少用到, 因此不详细描述.
#### 3.1.1. hasPrevious() 方法
- 定义:
`boolean hasPrevious()`
- 作用:
如果以逆向遍历列表, 列表迭代器有多个元素, 则返回 true.
- 注意:
反向遍历的前提是指针先指到的元素的前面有元素存在.
因此需要先调用 `next()` 将初始指针后移至少两位,
才能开始反向遍历(后移两位指向的是第二个元素).
#### 3.1.2. previous() 方法
- 定义:
`E previous()`
- 作用:
返回列表中的前一个元素.
- 备注:
可以重复调用此方法来迭代列表, 或混合调用 `next()` 来前后移动.
(注意交替调用 `next()` 和 `previous()` 将重复返回相同的元素).
## 4. Enumeration 接口
- 这个是在集合框架存在前, JAVA1 中带有的迭代器. 目前已淘汰, 很少使用.
新的实现应该优先考虑使用 `Iterator` 接口而不是 `Enumeration` 接口.
- 实现 `Enumeration` 接口的对象, 它生成一系列元素, 一次生成一个.
连续调用 `nextElement` 方法将返回一系列的连续元素.
### 4.1. Enumeration 接口中定义的方法
#### 4.1.1. hasMoreElements() 方法
- 定义:
`boolean hasMoreElements()`
- 作用:
测试此枚举是否包含更多的元素. 当且仅当此枚举对象至少
还包含一个可提供的元素时, 才返回 true; 否则返回 false.
#### 4.1.2. nextElement() 方法
- 定义:
`E nextElement()`
- 作用:
如果此枚举对象至少还有一个可提供的元素, 则返回此枚举的下一个元素.
### 4.2. Enumeration 接口使用实例
```java
Vector v = new Vector();
v.add("A");
v.add("B");
v.add("C");
v.add("D");
//用 Enumeration 迭代器遍历集合
for (Enumeration e = v.elements(); e.hasMoreElements();){
System.out.println(e.nextElement());
}
5. 深入分析 for-each 和迭代删除操作
5.1. for-each 的底层操作原理
- 通常来说, 增强 for 循环能够适应很多情况下的集合遍历, 直接使用即可.
5.1.1. for-each 操作数组
实际上底层还是使用的是普通
for
循环, 如下实例所示.编译前:
int[] arr = {1, 2, 3}; for(int i : arr){ System.out.println(i); }
编译后反编译:
int [] arr = {1, 2, 3}; int ai[]; int k = (ai = arr).length; for(int j = 0; j < k; j++){ int i = ai[j]; System.out.println(i); }
5.1.2. for-each 操作 Iterable 实例
实际上底层操作的是
Iterator
迭代器对象.编译前:
List list = new ArrayList(); list.add("A"); list.add("B"); list.add("C"); for(Object ele : list){ System.out.println(ele); }
编译后反编译:
List list = new ArrayList(); list.add("A"); list.add("B"); list.add("C"); Object ele; for(Iterator iterator = list.iterator(); iterator.hasNext();){ ele = iterator.next(); System.out.println(ele); }
5.2. 并发修改异常
- 当需要一边迭代集合元素, 一百年删除指定的元素时,
只能使用迭代器, 不能使用for-each
进行操作.
5.2.1. 并发修改异常的产生原因
当使用迭代器的时候, 会在当前线程中, 再创建一个新线程.
迭代器在这个新的线程中对原来的集合进行遍历操作.因此迭代器所在的线程是和集合所在的线程是不一样的,
所以两个线程中的操作并不会互相同步.同时迭代器在新线程会建立一个指向原来对象的单链索引表.
若原集合中对象数量发生变化, 该索引表的内容并不会同步改变.所以在迭代过程中在集合线程使用集合的
remove()
方法删除元素时,
迭代器是不会知道的, 这时只有集合本身把元素删除了, 迭代器的索引表没有更新.然后在迭代器线程中, 当索引指针移到表中被删除元素本来的索引时,
就会找不到该索引所对应的迭代对象, 因为对象已经在集合中进行删除,
因此就会抛出并发修改异常ConcurrentModificationException
.
5.2.2. 并发修改异常的解决方案
这是会产生并发修改异常的代码
- 在用 for-each 遍历集合的过程中用集合方式删除元素
- 最总会提示并发修改异常
ConcurrentModificationException
.public static void main(String args[]) { List<String> famous = new ArrayList<String>(); famous.add("liudehua"); famous.add("madehua"); famous.add("liushishi"); famous.add("tangwei"); for (String s : famous) { if (s.equals("madehua")) { famous.remove(s); } } }
用迭代器进行元素删除, 避免并发修改异常出现.
- 使用迭代器
Iterator
中的remove
方法 - 即可以删除集合中的对象, 也能删除迭代器索引表对应索引
//不能使用foreach操作, 必须要获取迭代器 Iterator it = famous.iterator(); String s;
- 使用迭代器
//改用while循环 while(it.hasNext()) { s = it.next(); if (s.equals("madehua")) { it.remove();//这里用迭代器删除元素 } } ```