056.JAVA集合框架_集合的迭代操作


博主的 Github 地址


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();//这里用迭代器删除元素 } } ```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值