Java源码解析:ArrayList 和 Iterator 使用上的不同

1. 前言

ArrayList 是最常用的 List 类型,它提供了非常便捷的方法对数据进行增删改查,例如:add、remove、get、contains 等等。但是,实际应用中,我们经常需要在 for 循环判断数据,然后删除数据,ArrayList 在这方面的操作上是非常不方便的。下面我们看几个例子:

案例一: 在循环 for (String s : list) 进行删除数据:

public class MyList {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");

        for (String s : list) {
    
    
        	//当数据等于"c"时,删除当前数据
            if ("c".equals(s)){
    
    
                list.remove(s);
            }
            else {
    
    
                System.out.println(s);
            }
        }
    }
}

运行程序,抛出了java.util.ConcurrentModificationException异常。因此,这种方式是不行的。

案例二: 在循环 for (i = 0; i < size; i++) 删除数据:

		//同样,删除"c"数据
        for (int i = 0; i < list.size(); i++){
    
    
            String s = list.get(i);
            if ("c".equals(s)){
    
    
                list.remove(i);
            }
            else{
    
    
                System.out.println(s);
            }
        }

执行结果如下:

a
b
e

只输出了abe,丢失了数据 d。这是因为ArrayList内部维护一个elementData数组,删除数据后,此时i的索引值刚好是 d。然后我们又执行了 i++,下次输出变成 e。为了保证数据不丢失,必须在删除之后执行一次 i–

        for (int i = 0; i < list.size(); i++){
    
    
            String s = list.get(i);
            if ("c".equals(s)){
    
    
                list.remove(i);
                i--;   //这句不能丢掉
            }
            else{
    
    
                System.out.println(s);
            }
        }

案例三: 使用 iterator 删除数据(推荐):

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
    
    
            String s = iterator.next();
            if ("c".equals(s)){
    
    
                iterator.remove();
            }
            else{
    
    
                System.out.println(s);
            }
        }

输出结果如下:

a
b
d
e

从结果上看,这种方式没有任何问题。避免了开发人员对下标的操作,看上去代码也更直观、可读性更好。

2. iterator 对象是什么类型?

打开 ArrayList 源码,查看上面调用的 iterator() 方法。发现,其实就是 new Itr() 对象

    public Iterator<E> iterator() {
    
    
        return new Itr();
    }
  • Itr 是 ArrayList 的内部私有类,它实现了 Iterator 接口协议,因此也一样有 hasNext、next、remove 这几个常用方法。
  • Itr 通过维护 cursorlastRet 两个下标索引来操作数据
  • Itr 只能调用next()方法逐个获取数据,从第1个遍历到最后一个
private class Itr implements Iterator<E> {
    
    
        int cursor; 	  // 下一个数据的下标值
        int lastRet = -1; // 最后一次调用next()返回数据的下标值
        int expectedModCount = modCount;
		
		......
}

3. iterator 是怎么操作数据的?

通过上一步的源码得知,初始状态下,cursor = 0,lastRet = -1

在这里插入图片描述

iterator 每次获取下一个元素时,都要先调用 hasNext() 判断当前是否最后一个

        public boolean hasNext() {
    
    
            return cursor != size;
        }

iterator 调用 next() 获取cursor当前指向的元素,并将 cursor 移动到下一个元素

在这里插入图片描述

        public E next() {
    
    
            checkForComodification();
            int i = cursor;
            //如果当前已经没数据了,会抛出NoSuchElementException异常,所以要先调用hasNext()判断
            if (i >= size)
                throw new NoSuchElementException();
            //JAVA知识点:非static内部类怎么调用父类的属性?就是通过[父类.this.data]获取
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //cursor移动到下一笔元素
            cursor = i + 1;
            //将lastRet指向当前元素,并返回值
            return (E) elementData[lastRet = i];
        }

iterator 调用 remove() 删除 lastRet 指向的数据,并将 cursor 指向 lastRet 的位置。
因为这个操作已经将cursor指向正确的位置,因此不会丢失数据

在这里插入图片描述

        public void remove() {
    
    
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
    
    
                ArrayList.this.remove(lastRet); //调用ArrayList的remove方法
                cursor = lastRet;	//cursor 指向 lastRet 的位置
                lastRet = -1;       //lastRet 重置为-1,因此不能连续调用2次remove
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
    
    
                throw new ConcurrentModificationException();
            }
        }

4. 总结

  • ArrayList 可以通过任意下标值获取数据、插入/添加数据、删除数据,操作灵活性最高,当时循环中执行remove不方便
  • Iterator 只能通过 next() 逐个获取下一个数据,灵活性差一点,适用于循环中remove数据
  • Iterator 执行完 remove 之后,不能马上第二次执行 remove,必须先调用 next 才能再次 remove

猜你喜欢

转载自blog.csdn.net/qq_28834355/article/details/114091320
今日推荐