Java从入门到放弃(七)集合框架之ArrayList的坑

1、快速失败和安全失败

    1)、快速失败

        在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出ConcurrentModificationException。如下:

        List list = new ArrayList<>();
        for(int i = 0;i < 10;i++){
            list.add(i);
        }
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            Integer next = iterator.next();
            if(next==3){
                list.remove(6);
            }
            System.out.println(next);
        }
输出结果为:
0
1
2
3
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:939)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:893)
at Main.main(Main.java:7)

原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值(具体可看Java从入门到放弃(五)集合框架之ArrayList源码(2))。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

所以不要在迭代遍历的时候直接删除集合内的元素,如果要删除可以使用迭代器内部的方法:

        public void remove() {
            if (lastRet < 0)            
                throw new IllegalStateException();
            checkForComodification();   检查modCount的值是否发生改变

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;    //更新expectedModCount的值,
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

因为内部的remove方法会同步更新expectedModCount的值,使用不会发生快速失败的异常。

2)、安全失败

      安全失败是在迭代的时候对集合进行更新的时候不操作原集合,而是把集合进行拷贝一份,在复制的集合上进行修改操作,修改完之后再更改集合对象的引用。所以就避免了ConcurrentModificationException异常,

       不过因为不是再原集合上操作,所以在迭代的时候如果对集合进行了修改,迭代器是不能检测到的,

3)、应用场景

    java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改),如:ArrayList,LinkedList

    java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。如:CopyOnWriteArrayList。

2、包装类的坑

        Integer delete = 6;
        List list = new ArrayList<>();
        for(int i = 0;i < 10;i++){
            list.add(i);
        }
        list.remove(delete);
        System.out.println(list.toString());     //[0,1,2,3,4,5,7,8,9]
因为remove重载有两个方法,一个remove(int),一个是remove(object),代码中的delete是Integer类型,所以会视为Object,直接删除数组中的6这个元素,而不是索引为6的元素。
        long delete = 6;
        List list = new ArrayList<>();
        for(int i = 0;i < 10;i++){
            list.add(i);
        }
        list.remove(delete);
        System.out.println(list.toString());    //[0,1,2,3,4,5,6,7,8,9]

这里的delete虽然是基本数据类型,但是remove(int)方法的参数是int,long不能自动转换为int类型,需要强制转换,所以list.remove(delete)是会使用list.remove(object)方法,因为参数是Long类型,集合内数据是Integer类型,所以找不到对应的相等的元素,没有删除任何元素。

3、ListIterator和Iterator的区别

    Iterator迭代器的方法:
        hasNext():判断迭代器指向的位置后面是否还有元素,有就返回true,没有返回false;
        next():取出迭代器指向位置后面的一个元素;
        remove():删除迭代器指向位置的元素。
        checkForComodification():检查快速失败的方法;
    ListIterator迭代器的方法:
        add(E e): 将指定的元素插入列表,插入位置为迭代器当前位置之前
        hasNext():以正向遍历列表时,如果列表迭代器后面还有元素,则返回 true,否则返回false
        hasPrevious():如果以逆向遍历列表,列表迭代器前面还有元素,则返回 true,否则返回false
        next():返回列表中ListIterator指向位置后面的元素
        nextIndex():返回列表中ListIterator所需位置后面元素的索引
        previous():返回列表中ListIterator指向位置前面的元素
        previousIndex():返回列表中ListIterator所需位置前面元素的索引
        remove():从列表中删除next()或previous()返回的最后一个元素
        set(E e):从列表中将next()或previous()返回的最后一个元素返回的最后一个元素更改为指定元素e
  异同点:

        1、Iterator适用于全部的集合类,而ListIterator只能适用于List及其之类使用

        2、Iterator迭代器只能向后遍历,而ListIterator不仅可以向后遍历,也能向前遍历。

        3、ListIterator可以定位当前位置的索引,而Iterator不能

        4、ListIterator可以remove,add,set方法对集合进行修改,而Iterator只有remove方法;

你可能感兴趣的:(java从入门到放弃)