在多数情况下使用asList或subList都是只读的,因此一般不会出现问题,但当有需要对list 数据修改时就会抛出异常。
1.asList
有时在对一个数据进行一些处理时,数组没有 List 一些快捷方法,此时就需要将数据转为 List 使用,因此使用不当而引发程序异常
1.1.正常使用
import java.util.Arrays; import java.util.List; public class Test { public static void main(String[] args) { Integer[] numbers = new Integer[] {1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(numbers); System.out.println("包含 3:" + list.contains(3)); System.out.println("包含 6:" + list.contains(6)); } }
结果输出:
1.2.异常使用
当需要对 list 进行操作时会抛出异常
import java.util.Arrays; import java.util.List; public class Test { public static void main(String[] args) { Integer[] numbers = new Integer[] {1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(numbers); System.out.println("包含 3:" + list.contains(3)); System.out.println("包含 6:" + list.contains(6)); // 异常操作 list.add(7); } }
1.3.原因分析
通过查看Arrays 源码发现,asList 里面返回的 ArrayList 是位于 Arrays 里面的一个私有类
这个类和java.util.ArrayList一样,也继承了AbstractList
在 Arrays.ArrayList里面,它重写了 set、get、indexOf、contains等方法但没有重写 add 方法,这导致无法对 list 进行元素增加
因此,对于 asList 返回的 list 我们可以用来修改、获取等操作,但是无法增加元素和删除元素,因为 asList 引用的内部类 ArrayList 没有重写 add 和 remove 这些方法
1.4 衍生问题
对于这个问题进行深度研究发现,list 和numbers其实是公用的一个对象,对 list 操作其实就是对numbers操作,例如:将 list 第一个元素改为 9,其对应的 numbers第一个元素也将会变为 9
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Test { public static void main(String[] args) { Integer[] numbers = new Integer[] {1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(numbers); System.out.println("list包含 3:" + list.contains(3)); System.out.println("list包含 6:" + list.contains(6)); System.out.println("list包含 9:" + list.contains(9)); list.set(0,9); System.out.println("list包含 9:" + list.contains(9)); System.out.println("numbers[0]:" + numbers[0]); } }
结果:
2.subList
事故背景:原先有个业务需要对一批数据订阅,订阅数据是分段发送的,比如一次查询 1000 条,订阅的时候需要每次只发不超过 100 条,这个业务代码跑了很久一直没问题,直到一次功能迭代,新增了一个订阅,需要将这 1000 条数据和另外的几十条比较特殊的数据放在一起生成文件上传的 FTP,开发人员直接将需要加入的数据用 addAll 给加到原集合里面,然后再使用子集合时就发现程序抛出java.util.ConcurrentModificationException异常
1.1 原集合结构变更会引发异常
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { List<Integer> sourceList = new ArrayList<Integer>() {{ add(1); add(2); add(3); add(4); add(5); }}; List<Integer> subList = sourceList.subList(0, 3); System.out.println("sourceList: " + sourceList); System.out.println("subList: " + subList); sourceList.add(6); System.out.println("subList: " + subList); } }
结果:这种情况也只是修改原集合结构后使用了子集合才会抛异常
1.2子集合结构变更会影响原集合(注意元素位置)
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { List<Integer> sourceList = new ArrayList<Integer>() {{ add(1); add(2); add(3); add(4); add(5); }}; List<Integer> subList = sourceList.subList(0, 3); System.out.println("sourceList: " + sourceList); System.out.println("subList: " + subList); subList.add(6); System.out.println("sourceList: " + sourceList); System.out.println("subList: " + subList); } }
结果:在子集合末尾追加的元素,在原集合中的位置取决于子集合的toIndex
1.3 原集合或子集合数据变更会相互影响
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { List<Integer> sourceList = new ArrayList<Integer>() {{ add(1); add(2); add(3); add(4); add(5); }}; List<Integer> subList = sourceList.subList(0, 3); System.out.println("sourceList: " + sourceList); System.out.println("subList: " + subList); sourceList.set(0,2); System.out.println("sourceList: " + sourceList); System.out.println("subList: " + subList); subList.set(0,3); System.out.println("sourceList: " + sourceList); System.out.println("subList: " + subList); } }
结果:修改了原集合元素子集合也会修改,修改了子集合原集合也会修改
1.4 总结
- 修改原集合结构再使用子集合会抛异常
- 修改子集合结构原集合也会随之修改,追加元素的位置取决于toIndex
- 修改原集合的元素会影响子集合,修改子集合元素会影响原集合