java集合主要是指java.util包下面的Collection接口和Map接口,包含了常用的数据结构,主要有4个部分:List列表、Set集合、Map映射、工具类(Arrays和Collections)
1 集合类图
2 List
2.1 Vector
- a 线程安全*
- b 性能低*
2.2 ArrayList
*a ArrayList内部是通过数组实现的,它允许对元素进行快速随机访问。缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,也就是将数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动,代价比较高。所以ArrayList适合随机查找和遍历,不适合插入和删除。
*b 非线程安全。
LinkedList
a LinkedList实现了List和Deque接口,因此是一个双向链表, 可以当作堆栈、队列和双向队列使用。当数据量很大或者操作很频繁的情况下,添加和删除元素时具有比ArrayList更好的性能,因为ArrayList要移动数据 。但在元素的查询和修改方面要弱于ArrayList,因为LinkedList要移动指针。LinkedList类每个结点用内部类Node表示,通过first和last引用分别指向链表的第一个和最后一个元素,当链表为空时,first和last都为NULL值。
b LinkedList是非线程安全的。
-
c 方法
- boolean add(E e) :在链表尾部添加一个元素,如果成功,返回true,否则返回false。
- void addFirst(E e) :在链表头部插入一个元素。
- addLast(E e) :在链表尾部添加一个元素。
- void add(int index, E element) :在指定位置插入一个元素。
- boolean remove(Object o) :从当前链表中移除指定的元素。
- E remove(int index) :从当前链表中移除指定位置的元素。
- E removeFirst() :从当前链表中移除第一个元素。
- E removeLast() :从当前链表中移除最后一个元素。
- E remove() :从当前链表中移除第一个元素,作用等同removeLast()。
3 Set
3.1 TreeSet, LinkedHashSet, HashSet 的区别
- 都是非线程安全的;
- TreeSet的主要功能用于排序,内部结构是一个树结构(红黑树),元素是有序的,添加、删除操作时间复杂度为O(log(n)),存放的对象所属的类必须实现Comparable接口;
- LinkedHashSet的主要功能用于保证FIFO即有序的集合(先进先出);
- HashSet使用哈希表实现的,并发度是16,负载因子0.75,元素是无序的。添加、删除操作时间复杂度都是O(1);
- HashSet插入数据最快,其次LinkHashSet,最慢的是TreeSet,因为TreeSet内部要实现排序;
- HashSet不保证有序,LinkHashSet保证FIFO即按插入顺序排序,TreeSet安内部实现排序,也可以自定义排序规则;
- HashSet和LinkHashSet允许存在null数据,TreeSet中插入null数据时会报NPE(null无法排序);
4 Queue
Queue表示队列,实现了一个先进先出(FIFO:First In First Out)的有序表。常用方法:
- boolean add(E)/ boolean offer(E):添加元素到队尾;
- E remove()/ E poll():获取队首元素并从队列中删除;
- E element()/ E peek():获取队首元素,但并不从队列中删除。
添加、删除和获取队列元素有两个方法(有容量限制的队列),这是因为在添加或获取元素失败时,这两个方法的行为是不同的:
操作 | 抛异常 | 返回true/null |
---|---|---|
添加元素到队尾 | add(E e) | boolean offer(E e) |
取队首元素并删除 | E remove() | E poll() |
取队首元素但不删除 | E element() | E peek() |
注意:避免将null添加到队列(有容量限制的队列)中,否则poll()方法返回null时,无法确定是取到了null元素还是队列为空。
4.1 ArrayBlockingQueue
ArrayBlockingQueue是一个线程安全的阻塞式的有界队列,有界意味着,它不能够存储无限多数量的对象。所以在创建 ArrayBlockingQueue 时,必须要给它指定一个队列的大小。底层以数组的形式保存数据。
默认情况下,不保证顺序。但是,如果将构造方法中的fair参数设置为true的话,此时构造的队列以FIFO顺序访问。ArrayBlockingQueue中比较重要的方法:
- add(E e):把元素e添加加到 BlockingQueue,如果BlockingQueue的容量可以容纳该元素,则返回 true,否则报IllegalStateException异常;
- offer(E e):将元素e加到BlockingQueue,如果BlockingQueue可以容纳的话,则返回 true,否则返回 false ;
- put(E e):把元素e加到 BlockingQueue 里,如果 BlockQueue 没有空间,则调用此方法的线程被阻塞,直到 BlockingQueue里面有空间再继续后续操作;
使用的时候需要注意,防止队列满了之后继续添加对象造成阻塞。 - poll(timeout):获取队列里排在首位的对象,如果队列没有元素,则等timeout参数规定的时间,取不到时返回 null ;
- take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,则调用此方法的线程被阻塞,直到BlockingQueue有新的对象被加入为止 ;
- remove():删除元素,需要将删除元素后的元素统一迁移一个单位,并且在操作过程中会获得锁,对性能有影响,因此不应轻易执行remove操作
- ArrayBlockingQueue 使用场景。
- 可保证先进先出(FIFO)队列,队列头是最先进队的元素,队列尾是最后进队的元素;
- 有界队列,即初始化时指定的容量,就是队列最大的容量,不会出现扩容,容量满,则阻塞 进队操作,容量空,则阻塞出队操作;
- 队列不支持null元素
4.2 LinkedBlockingQueue
一个阻塞式的有界队列,保存元素的是一个链表,队列默认的最大长度为Integer.MAX_VALUE。其内部有一个Node的内部类,其中有一个成员变量 Node next。就这样形成了一个链表的结构,要获取下一个元素,只要调用next就可以了。
LinkedBlockingQueue VS ArrayBlockingQueue
- LinkedBlockingQueue是基于链表实现的,初始化是可以不用指定队列大小(默认是Integer.MAX_VALUE);ArrayBlockingQueue是基于数组实现的初始化时必须指定队列大小;
- LinkedBlockingQueue在put操作都会生成新的Node对象,take操作Node对象在某一时间会被GC,可能会影响GC性能;ArrayBlockingQueue是固定的数组长度循环使用,不会出现对象的产生与回收;
- LinkedBlockingQueue是基于链表的形式,在执行remove操作时,不用移动其他数据; ArrayBlockingQueue是基于数组,在remove时需要移动数据,影响性能;
- LinkedBlockingQueue使用两个锁将put操作与take操作分开;ArrayBlockingQueue使用一个锁来控制,在高并发高吞吐的情况下,LinkedBlockingQueue的性能较好。
- 都不允许null元素
Deque
读音: deck
父接口: Collection<E>, Iterable<E>, Queue<E>; 子接口: BlockingDeque<E>;实现类: ArrayDeque, ConcurrentLinkedDeque, LinkedBlockingDeque, LinkedList
Deque允许两头都进,两头都出,这种队列叫双端队列(Double Ended Queue),提供了如下功能:
- 既可以添加元素到队尾,也可以添加元素到队首;
- 既可以从队首获取元素,又可以从队尾获取元素。
- Deque VS Queue
操作 | Queue | Deque |
---|---|---|
添加元素到队尾 | add(E e) / offer(E e) | addLast(E e) / offerLast(E e) |
取队首元素并删除 | E remove() / E poll() | E removeFirst() / E pollFirst() |
取队首元素但不删除 | E element() / E peek() | E getFirst() / E peekFirst() |
添加元素到队首 | 无 | addFirst(E e) / offerFirst(E e) |
取队尾元素并删除 | 无 | E removeLast() / E pollLast() |
取队尾元素但不删除 | 无 | E getLast() / E peekLast() |
5 Deque 的实现类主要分为两种场景:
- 非并发场景
- LinkedList 大小可变的链表双端队列,允许元素为 null
- ArrayDeque 大小可变的数组双端队列,不允许 null
并发场景
- LinkedBlockingDeque 如果队列为空时,获取操作将会阻塞,直到有元素添加。
6 Map类图
6.1 TreeMap, LinkedHashMap, HashMap 的区别
- LinkedHashMap是继承于HashMap,拥有HashMap所有特性,比HashMap多了双向链表,存取数据还是跟HashMap一样使用的Entry[]的方式,双向链表只是为了保证顺序。
- LinkedHashMap有序,可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入);
- LinkedHashMap是线程不安全的;
- HashMap无序,非线程安全;
- TreeMap的用法主要是排序, key必须实现Comparable接口,默认的排序为升序,如果要改变其排序需要自己实现一个Comparator。
LinkedHashMap默认按照插入顺序的,也就是插入的是什么顺序,读出来的就是什么顺序。同时也提供了按照访问顺序的构造方法,方法参数是accessOrder,需设置为true。
Map<String, String> linkedHashMap = new LinkedHashMap<>(16, 0.75f, true);
linkedHashMap.put("key1", "value1");
linkedHashMap.put("key2", "value2");
linkedHashMap.put("key3", "value3");
Set<Map.Entry<String, String>> set = linkedHashMap.entrySet();
Iterator<Map.Entry<String, String>> iterator = set.iterator();
System.out.println("首次遍历");
while(iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println("key: " + key + ",value: " + value);
}
System.out.println(linkedHashMap.get("key1"));
Set<Map.Entry<String, String>> set2 = linkedHashMap.entrySet();
Iterator<Map.Entry<String, String>> iterator2 = set2.iterator();
System.out.println("再次遍历");
while(iterator2.hasNext()) {
Map.Entry<String, String> entry = iterator2.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println("key: " + key + ",value: " + value);
}
}
// 首次遍历
// key: key1,value: value1
// key: key2,value: value2
// key: key3,value: value3
// value1
// 再次遍历
// key: key2,value: value2
// key: key3,value: value3
// key: key1,value: value1
6.2 EnumMap
HashMap是一种通过对key计算hashCode(),直接定位到value所在的内部数组的索引。
EnumMap是给枚举使用的,key为枚举,它在内部以数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),效率最高。
public V put(K key, V value) {
typeCheck(key);
int index = key.ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
public V get(Object key) {
return (isValidKey(key) ? unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
}
7 Arrays使用
7.1 避免使用基本数据类型数组转换为列表
public static void main(String[] args) {
int[] ints = {1,2,3,4};
List list = Arrays.asList(ints);
System.out.println("size of list:" + list.size());
}
size of list:1
Arrays.asList(T...a)源码:
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with {@link Collection#toArray}. The returned list is
* serializable and implements {@link RandomAccess}.
*
* <p>This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
* <pre>
* List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
* </pre>
*
* @param <T> the class of the objects in the array
* @param a the array by which the list will be backed
* @return a list view of the specified array
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
asList方法的参数接受的是一个泛型的变长参数,但java中8种基本数据类型(byte,short,int,long,float,double,char,boolean)是无法泛型化的,也就是说8个基本类型是无法作为asList的参数的, 要想作为泛型参数就必须使用其所对应的包装类型。
既然要使用包装类型,那为什么上述demo没有报错呢??
因为该实例是将int类型的数组当做其参数,而在Java中数组是一个对象,它是可以泛型化的,所以该例子没有报错。上面的例子是将整个int类型的数组当做泛型参数,那么经过asList转换就只有一个int 的列表了。
public static void main(String[] args) {
int[] ints = {1,2,3,4};
List list = Arrays.asList(ints);
System.out.println("type of List:" + list.get(0).getClass());
System.out.println("list.get(0) == ints:" + list.get(0).equals(ints));
Integer[] ints2 = {1,2,3,4};
List list2 = Arrays.asList(ints);
System.out.println("size of list2:" + list2.size());
System.out.println("type of list2.get(0):" + list2.get(0).getClass());
System.out.println("list2.get(0) == ints2[0]:" + list2.get(0).equals(ints2[0]));
}
type of list :class [I
list.get(0) == ints:true
size of list2:4
type of list2.get(0) :class java.lang.Integer
list2.get(0) == ints2[0]:true
7.2 asList方法返回的列表不可变
public static void main(String[] args) {
Integer[] ints = {1,2,3,4};
List list = Arrays.asList(ints);
list.add(5);
}
java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
...
}
通过源码可以看出,asList返回的结果并不是java.util.ArrayList, 而是Arrays的内部类,该类的add、remove等改变结果的方法从AbstractList父类继承过来,但是这些方法直接抛出UnsupportedOperationException异常,因此并没有list的基本变长特性。该内部list是一个长度不可变的列表,传入参数的数组大小是多少,其返回的列表就大小就是多少。
7.3 常用方法
1 toString()
int[] array = new int[]{1, 2, 3};
System.out.println(Arrays.toString(array)); // [1, 2, 3]
int[][] deepArray = new int[][]{{1, 3},{2, 4}};
System.out.println(Arrays.toString(deepArray)); // [[I@47c62251, [I@3e6fa38a]
// 多维数组时,需要使用deepToString转换
System.out.println(Arrays.deepToString(deepArray)); // [[1, 3], [2, 4]]
2 fill填充
int[] array = new int[5];
Arrays.fill(array, 2);
System.out.println(Arrays.toString(array)); //[2, 2, 2, 2, 2]
array = new int[5];
Arrays.fill(array, 1, 4, 2); //部分填充, 后三个参数分别表示开始索引(包含),结束索引(不包含),填充值
System.out.println(Arrays.toString(array)); //[0, 2, 2, 2, 0]
3 sort
int[] array = new int[]{3, 10, 4, 0, 2};
Arrays.sort(array); // 串行排序
System.out.println(Arrays.toString(array)); //[0, 2, 3, 4, 10]
array = new int[]{3, 10, 4, 0, 2};
Arrays.parallelSort(array); // 并行排序
System.out.println(Arrays.toString(array)); // [0, 2, 3, 4, 10]
array = new int[]{3, 10, 4, 0, 2};
Arrays.sort(array, 0, 4); //部分排序,后两个个参数分别表示开始索引(包含),结束索引(不包含)
System.out.println(Arrays.toString(array)); // [0, 3, 4, 10, 2]
4 binarySearch: 二分查询
在调用该方法之前,必须先调用 Arrays.sort() 方法进行排序,如果数组没有排序,那么结果是不确定的,此外如果数组中包含多个指定元素,则无法保证将找到哪个元素
当搜索元素是数组元素时,返回该元素的索引值;当搜索元素不是数组元素时,返回 - (索引值 + 1)
Integer[] data = {1, 3, 5, 7};
Arrays.sort(data); // 搜索元素是数组元素,返回该元素索引值
System.out.println(Arrays.binarySearch(data, 1)); // 0
Integer[] data = {1, 3, 5, 7};
Arrays.sort(data); // 搜索元素不是数组元素,且小于数组中的最小值,此时程序会把数组看作 {0, 1, 3, 5, 7},0的索引值为0,则搜索0时返回 -(0 + 1) = -1
System.out.println(Arrays.binarySearch(data, 0));
Integer[] data = {1, 3, 5, 7};
Arrays.sort(data); // 搜索元素不是数组元素,且大于数组中的最大值,此时程序会把数组看作 {1, 3, 5, 7, 9},9的索引值为4,则搜索8时返回 -(4 + 1) = -5
System.out.println(Arrays.binarySearch(data, 9));
Integer[] data = {1, 3, 5, 7};
Arrays.sort(data); // 搜索元素不是数组元素,但在数组范围内,此时程序会把数组看作 {1, 2, 3, 5, 7},2的索引值为1,则搜索2时返回 -(1 + 1) = -2
System.out.println(Arrays.binarySearch(data, 2));
5 数组复制
int[] array = new int[]{3, 10, 4, 0, 2};
int[] arrayCopy = Arrays.copyOf(array, 3); // 第二个参数为新数组的长度
out.println(Arrays.toString(arrayCopy)); // [3, 10, 4]
arrayCopy = Arrays.copyOf(array, 7);
out.println(Arrays.toString(arrayCopy)); //[3, 10, 4, 0, 2, 0, 0], 多出的长度补0
arrayCopy = Arrays.copyOfRange(array, 1, 4); // 后两个个参数分别表示开始索引(包含),结束索引(不包含)
out.println(Arrays.toString(arrayCopy)); // [10, 4, 0]
6 对数组元素采用指定的方法计算
Integer[] data = {1, 2, 3, 4};
Arrays.setAll(data, i -> data[i] * 2); // i为索引值
System.out.println(Arrays.toString(data)); // [2, 4, 6, 8]
Integer[] data = {1, 2, 3, 4};
Arrays.parallelSetAll(data, i -> data[i] * 2); // i为索引值
System.out.println(Arrays.toString(data)); // [2, 4, 6, 8]
Integer[] data = {2, 3, 4, 5};
// 第一个元素2不变,将其与第二个元素3一起作为参数x, y传入,得到乘积6,作为数组新的第二个元素
// 再将6和第三个元素4一起作为参数x, y传入,得到乘积24,作为数组新的第三个元素,以此类推
Arrays.parallelPrefix(data, 0, 3, (x, y) -> x * y);
System.out.println(Arrays.toString(data)); // [2, 6, 24, 5]
8 subList注意事项
8.1 ArrayList.subList只是原始ArrayList的一个视图
官网文档:
Returns a view of the portion of this list between the specified
- {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If
- {@code fromIndex} and {@code toIndex} are equal, the returned list is
- empty.) The returned list is backed by this list, so non-structural
- changes in the returned list are reflected in this list, and vice-versa.
- The returned list supports all of the optional list operations.
我们先看一个例子
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
//通过构造函数新建一个包含list1的列表list2
List<Integer> list2 = new ArrayList<>(list1);
//通过subList生成一个与list1一样的列表list3
List<Integer> list3 = list1.subList(0, list1.size());
//修改list3
list3.add(3);
System.out.println("list1 == list2:" + list1.equals(list2)); // true ??
System.out.println("list1 == list3:" + list1.equals(list3)); // false ??
}
答案是这样的:
list1 == list2:false
list1 == list3:true
我们看一下subList的实现:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
// 该SubLsit是ArrayList的内部类,它与ArrayList一样,都是继承AbstractList和实现RandomAccess接口。同时也提供了get、set、add、remove等list常用的方法
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
//parent就是在前边传递过来的list,也就是说this.parent就是原始list的引用
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
public int size() {
checkForComodification();
return this.size;
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
// 因为this.parent就是原始list的引用,所以add方法影响的是原始的list
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
....
}
通过源码,自然解释了list1 == list2:false;list1 == list3:true这个结果。
总结: subList生成的子列表只是原列表的一个视图而已,如果我们操作子列表它产生的作用都会表现在原列表上面。
8.2 避免操作生成subList的原列表
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
List<Integer> list2 = list1.subList(0, list1.size());
list1.add(3);
System.out.println(list1.size());
System.out.println(list2.size());
}
执行结果:
3
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
at java.util.ArrayList$SubList.size(ArrayList.java:1040)
public int size() {
checkForComodification();
return this.size;
}
private void checkForComodification() {
// 当原列表的modCount与this.modCount不相等时就会抛出ConcurrentModificationException
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
通过源码,我们知道SubList.modCount 在new的过程中继承了原列表modCount,只有在修改该SubList时才会修改该值,而在该实例中我们是操作原列表,原列表的modCount不会反应在子列表的modCount上,所以抛出该异常。
9 Collections使用
9.1 转化为数组
ArrayList<String> list = new ArrayList<>();
...
Object[] a1 = list.toArray();//将List对象转换成Object数组
String[] a2 = list.toArray(new String[list.size()]);
String[] a2 = list.toArray(new String[0]); //当参数数组长度为0时,会重新分配一个数组
9.2 排序
- reverse(List):反转List中元素的顺序;
- shuffle(List):对List集合元素进行随机排序;
- sort(List):根据元素的自然顺序对指定List集合元素按升序排序;
- sort(List, Comparator):根据指定的Conparator产生的顺序对List集合进行排序;
- swap(List, int i, int j):将指定List集合中的i和j处的元素进行交换;
9.3 替换所有的元素:fill
Collections.fill(list, "a");
9.4 查找元素在集合中出现的次数:frequency
int n = Collections.frequency(list, 6)
9.5 遍历删除
// 初始化集合
ArrayList<String> list=new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
//下面是错误的遍历删除方式,抛出异常:java.util.ConcurrentModificationException
for(String s:list){
list.remove(s);
}
Iterator<String> it = list.iterator();
while(it.hasNext()){
String x = it.next();
list.remove(x);
}
// 正确的遍历方式
for(Iterator<String> it=list.iterator();it.hasNext();){
it.next();
it.remove();
}
System.out.println("list = " + list.size());
// 倒叙方式可以正常删除
for(int i=list.size()-1; i>=0; i--){
list.remove(i);
}
// 下面方式不会报异常,但只能遍历删除部分元素,因为当一个元素被删除时,列表的大小缩小并且下标变化,所以想要在一个循环中用下标删除多个元素的时候,不会生效
for(int i=0;i<list.size();i++){
list.remove(i);
}
为什么ArrayList在使用迭代器迭代元素时不能使用List.remove()删元素,而是使用Iterator.remove()删元素。
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
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;
...
}
public E next() {
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;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
当ArrayList调用iterator()方法,初始化iterator时,将list的modCount值赋给expectedModCount,如果expectedModCount和modCount的值一直相等,iterator.next()是不会抛并发修改异常的, 继续看List.remove(Object)源码:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
可以看出list.remove()被调用时,方法中modCount自增了,而expectedModCount是没有修改的,因此,在下一次iterator调用next()时,将抛出ConcurrentModificationException。