【收割offer】面试100题之Java基础(一)

136 阅读2分钟

1.ArrayList如何去重?

hashSet(不保证元素顺序) LinkedHashSet(顺序一致)

2.集合遍历当中如何删除指定元素?

1. 通过迭代器删除(实际项目中推荐)

while (iterator.hasNext()) {
    Student student = iterator.next();
    if ("male".equals(student.getGender())) {
        iterator.remove();
        //list.remove(male),在删除【除开倒数第二个】元素时会产生ConcurrentModificationException异常
    }
}

为什么不通过迭代器自带的方法就会ConcurrentModificationException异常? 下面要介绍一下迭代器:

  • 迭代器介绍 755171-20171205235300519-145576146.png

当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove()方法删除上一次next()方法返回的集合元素才可以;否则会引发java.util.ConcurrentModificationException异常。

之所以会出现这样的异常,是因为Iterator迭代器采用的是快速失败(fast-fail)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中其它线程修改),程序立即引发ConcurrentModificationException,

迭代器是如何知道被修改了呢?为何删除倒二元素不会异常?

首先看一下迭代器的源码:

private class Itr implements Iterator<E> {
    int cursor;       // 光标 index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount; //迭代器初始化时候设置的,一旦容器结构发生变化,会改变modCount的值,进而引发后面的异常

    public boolean hasNext() {
        return cursor != size;
    }
        
    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }

        ...
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        }
    }
}

可以清楚的看到迭代器里维护了cursor,expectedModCount两个字段。modCount则由list维护。 一旦容器结构发生变化,modCount的值会发生变化,每次累加1,modCount相当于一个记录ArrayList版本的变量expectedModCount在生成迭代器时候进行初始化,代表初始化时候容器的modCount。

每次next()方法执行完之后,cursor都代表当前元素的下一个元素的下标。 在hasNext()方法中,是将光标与size对比,删除倒二后cursor == size,故没有异常;

为何使用迭代器删除不会异常?

因为在迭代器的remove()方法里,expectedModCount = modCount,重置了expectedModCount;

2. 在高级for循环中,直接list.remove(int i);

for (int a:list) {
    if(a == 1){
        list.remove(a);
        //break;
    }
}

如果在list.remove(a)后没有break,而是继续遍历,则会报错java.util.ConcurrentModificationException

3. 普通for循环,直接list.remove(int i);

list.remove()只是删除元素,不会改变原有元素的位置,举例:remove(nums[1]),指向原nums[2]的值,i++后指向nums[3]

参考: 迭代器Iterator与ConcurrentModificationException详解

为什么Iterator的remove方法可保证从源集合中安全地删除对象,而在迭代期间不能直接删除集合内元素