这些List集合“神坑”,你有遇到过吗?
在日常工作中,当然我这里指的是 java 程序员工作,我们几乎所有的业务代码都离不开 List 集合,List 是我们经常使用的集合类之一,那么问题来了,你在使用 List 集合过程中有没有遇到过这样或那样的坑呢,今天就由我来分享下,我在工作中遇到的 List 集合的几个神坑。
首先在这里简单提及下 Array、 List、 ArrayList 的基本知识。
Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的,Array 获取数据的时间复杂度是 O(1)。但是要删除数据却是开销很大,因为这需要重排数组中的所有数据。可以利用Arrays.asList返回一个列表。
List 是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。注意!List 是一个接口。
ArrayList 类是继承 AbstractList 抽象类和实现List接口的一个实现类。由于 List 不能创建实例对象,我们可以利用 ArrayList 实现类的实例对象充当List接口的对象引用。另外ArrayList可以利用toArray方法返回一个数组。
下面开始数坑。
第一个坑:Arrays.asList 方法返回的 List 不支持增加、删除操作。
给各位一分钟看看下面这段代码有什么问题?

时间到!我就直说了。这段代码表面看起来没有任何问题,编译也能通过,但是真正测试运行的时候将会在第3行抛出如下异常:
为什么会这样呢?我们回顾下上面的知识,Arrays.asList返回一个列表,要注意的是,这里的列表和ArrayList不是同一个哟,它只是Arrays的一个内部类。通过查看java.util.ArrayList和java.util.Arrays$ArrayList的类图发现add/remove等方法实际都来自AbstractList,而java.util.Arrays$ArrayList并没有重写父类的方法。而父类方法恰恰都会抛出UnsupportedOperationException。
第二个坑,Arrays.asList 方法返回的新 List 和该方法原始入参数组修改会相互影响。
再给各位一分钟快速看看这段代码,你猜输出结果是什么?

不卖关子了,直接上结果。

诶,integerList 的1,2呢?原来是 subList 直接使用的原始的数组,也就是说subList 和integerList共享了数组。这还得了,都是在共享数组了,那彼此在进行数据修改时极有可能会产生一些意想不到的 Bug。说到这里,那咱们该怎么避免这类 Bug 呢?标准答案来咯。
方法一将Arrays.asList作为ArrayList构造方法的参数重新 new 一个 List 即可。方法二通过 Guava 库中的Lists.newArrayList,将返回的新 List 和原始的数组解耦,就不会再互相影响了。
第三个坑,直接遍历 List 集合删除元素会报错。

上面的代码我们使用foreach方式遍历 List 集合,如果符合条件,将会从集合中删除改元素。这个程序编译正常,但是运行时,程序将会抛出如下异常:

这是为什么呢?咱们查看其源码可以发现,删除元素方法remove会使集合结构发生修改,也就是modCount(modCount来源于ArrayList的父类AbstractList,可以用来记录 List 集合被修改的次数)会修改,在循环过程中,会比较当前 List 的集合实际修改的次数modCount与迭代器修改的次数expectedModCount,刚开始遍历循环的时候expectedModCount==modCount,ArrayList#remove之后将会使modCount加一,expectedModCount与modCount将会不相等,这就导致迭代器遍历时将会抛ConcurrentModificationException异常。
解决方法主要有两种方式:
使用 ArrayList 的迭代器方式遍历,然后调用其中 remove 方法。
在 JDK 1.8+ 可以使用 removeIf 方法进行删除操作。
今天的分享就到这啦,欢迎各位友友前来交流指导,咱们互相学习,共同进步!
网友评论