Java集合学习记录——Iterator

1. Java集合类图


图片来源:菜鸟教程
  上图中,上图中实线边框的是实现类,折线边框的是抽象类,点线边框的是接口。
  从图中我们可以看出,Java集合主要包含了Collection(主要存储元素集合)和Map(主要存储键值对集合)两种类型。

2. Iterator

2.1 Iterator介绍

  从上图可以看出,Iterator是整个集合框架的起始点,因此,先学习这个Iterator。
  Iterator是Java集合中的迭代器,也是设计模式中的迭代器模式的实现,在Java遍历一个集合,除了使用for循环和增强for循环外,还可以使用迭代器来遍历集合(forEach循环实际也是使用了迭代器来遍历)。

  • Iterator 简单使用

    ArrayList为例,展示Iterator的基本使用

private static void test() {
    ArrayList<String> arrayList = new ArrayList<>();
    arrayList.add("1");
    arrayList.add("2");
    arrayList.add("3");

    Iterator iterator = arrayList.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}

  可以看到,Iterator使用中,首先调用iterator()方法,获取迭代器,然后,循环中使用hasNext()方法判断是否含有下一个元素,使用next()方法获取当前的元素。

2.2 Iterator源码

2.2.1 主要方法

Iterator中的主要方法有:

public interface Iterator<E> {
    // 判断是否含有下一个元素
    boolean hasNext();

	// 获取下一个元素
    E next();

	// 移除元素,主要靠实现类实现
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    
    //  Java8 新增方法,方便直接遍历
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

  在上边的使用中主要使用前两个方法,除了这两个方法外,Iterator中还有一个remove()方法,主要依赖实现类实现;另外还有一个Java8新增的方法forEachRemaining()可以直接使用该方法遍历集合,可以看到该方法的默认实现和上边例子中的使用相同。

  forEachRemaining的使用:

private static void test() {
    ArrayList<String> arrayList = new ArrayList<>();
    arrayList.add("1");
    arrayList.add("2");
    arrayList.add("3");

    Iterator iterator = arrayList.iterator();
    iterator.forEachRemaining(a -> {
        System.out.println(a);
    });
}
2.2.2 Iterator原理理解

  Iterator迭代器的基本模型,可以理解为一个介于结合元素的中间位置的游标。如下图所示,调用hasNext()方法,会查看游标后边是否有数据,如果有就返回true,调用next()方法,游标往后移一位,到1和2之间的位置,同时返回游标前的元素。(该游标只能单向移动)

iterator游标简图

2.2.3 Iterator源码实现

  Iterator只是一个接口,具体方法的实现,还是在具体的集合类中,我们以ArrayList为例,查看其具体实现,ArrayList中使用了一个内部类Itr实现了Iterator接口。

  • 成员变量
private class Itr implements Iterator<E> {
    int cursor;       // 游标位置索引,默认为0
    int lastRet = -1; // 游标上一次所在位置
    // modCount:AbstractList中的成员变量,集合修改的次数;
    // expectedModCount: 集合期望修改的次数,默认和modCount相等
    int expectedModCount = modCount;
}
  • hasNext()方法:

    在上边我们知道该方法,主要判断集合中是否还有元素,也就是判断游标后边是否还有元素,而根据成员变量中的几个元素,猜测可以使用对应的索引来判断,也就是cursor是否小于集合的长度,具体实现:

public boolean hasNext() {
    return cursor != size;
}
  • next() 方法:

    在查看代码之前,猜测其实现方式:首先会将游标往后移,也就是cursor++, 然后,返回游标前的元素,在ArrayList中就是游标未修改之前的索引位置元素(LinkedList中没有实现Iterator接口)。

 public E next() {
      // 验证 expectedModCount 是否和 modCount相等
      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];
  }
  
  /**
  * 验证 expectedModCount 是否和 modCount相等;
  * modCount在修改集合(添加元素,删除元素时会修改)
  */
  final void checkForComodification() {
      if (modCount != expectedModCount)
          throw new ConcurrentModificationException();
  }

  根据以上代码,首先需要判断expectedModCountmodCount是否相等,如果不相等会抛出异常,也就是如果获取了迭代器之后,又修改了集合,那么使用迭代器的next()方法会报错;然后,就是常规操作,游标后移,返回之前索引的元素,为lastRet赋值;
  下边验证,获取迭代器之后,修改集合:

private static void test() {
    ArrayList<String> arrayList = new ArrayList<>();
    arrayList.add("1");
    arrayList.add("2");
    arrayList.add("3");

    Iterator iterator = arrayList.iterator();
    arrayList.add("4");
    iterator.forEachRemaining(a -> {
        System.out.println(a);
    });
}

运行之后,可以看到果然抛出了异常,因此,在使用迭代器时需要注意,不能在获取了迭代器之后仍然修改集合(添加、移除集合中的元素)。

Exception in thread "main" java.util.ConcurrentModificationException...
  • remove() 方法:
public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    
    // 判断 expectedModCount 和 modCount是否相等
    checkForComodification();

    try {
    	// 调用ArrayList中的remove方法,删除游标之前位置索引的元素,
    	// 也就是当前游标的前一个元素。
        ArrayList.this.remove(lastRet);
		// 游标移回上一次所在位置
        cursor = lastRet;
        // 游标上一次位置,置为 -1
        lastRet = -1;
        // 修改 expectedModCount 值,是它和modCount相等
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

  根据上边的代码,首先,判断lastRet是否小于0,然后,还是判断expectedModCountmodCount是否相等,然后调用ArrayListremove(int index)方法,但是,删除之后,modCount会被修改,因此需要让expectedModCount 和它再次相等,防止后边使用迭代器的相关方法报错;

  每次移除的是游标上一次所在位置索引的元素,同时判断lastRet不能小于0,而移除元素之后和最开始获取迭代器时,lastRet都是-1;也就是说,获取迭代器之后,不能立马移除元素,也不能在使用remove()方法后,不使用next()方法移动游标;

  同时移除之后,集合ArrayList中的所有元素会往前移一位,如果游标的位置不修改,就会导致发生遗漏元素;因此,游标需要重新指向之前所在位置。

  关于删除方法,还有一个常见问题,就是在循环中移除元素,这个问题,稍后在ArrayList的学习整理中记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值