前言
现有一个简化的需求,假设有一个点位列表["1", "2", "4", "5"]
,现在需要将该列表从中间分成两份:
- 序列1:
["1", "2"]
- 序列2:
["4", "5"]
分开后在序列1的末尾添加一个元素"3"
,变成["1", "2", "3"]
。
问题复现
在编写下面的测试代码运行后,结果并不是想要的:
public static void testSubList() {
List<String> origin = new ArrayList<>();
origin.add("1");
origin.add("2");
origin.add("4");
origin.add("5");
List<String> sub1 = origin.subList(0, 2);
sub1.add("3");
List<String> sub2 = origin.subList(2, origin.size());
// 打印结果
System.out.println(sub1);
System.out.println(sub2);
}
结果:
[1, 2, 3] //sub1
[3, 4, 5] //sub2
按需求来说,sub2中应该是["4", "5"]
,为什么在sub1中插入的"3"
也进去了呢?
subList方法解析
在查阅资料时,发现该篇文章做了说明:https://www.jianshu.com/p/69937e239d61。
总结来说,就是使用subList方法分割出来的子序列,仍然和原序列关联,在子序列上进行的增删操作,影响的其实是原序列。这也解释了上面代码出现的问题:
- 在sub1中插入元素"3"后,原序列变成了
["1", "2", "3", 4", "5"]
- sub2截取到的元素自然变成了
["3", 4", "5"]
同时,对于subList的不正确使用还会引发java.util.ConcurrentModificationException
异常。
复现异常
如果将sub1添加元素的操作放在切割sub2后,则会引发异常:
public static void testSubList() {
List<String> origin = new ArrayList<>();
origin.add("1");
origin.add("2");
origin.add("4");
origin.add("5");
List<String> sub1 = origin.subList(0, 2);
List<String> sub2 = origin.subList(2, origin.size());
sub1.add("3");
// 打印结果
System.out.println(sub1);
System.out.println(sub2);
}
[1, 2, 3]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1241)
at java.util.ArrayList$SubList.listIterator(ArrayList.java:1101)
at java.util.AbstractList.listIterator(AbstractList.java:299)
at java.util.ArrayList$SubList.iterator(ArrayList.java:1097)
at java.util.AbstractCollection.toString(AbstractCollection.java:454)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.example.nb_road.IndexTest.testSubList(IndexTest.java:118)
at com.example.nb_road.IndexTest.main(IndexTest.java:126)
意思是非线程安全的容器被非法修改了。
上面说到,子序列的修改会反应在原序列上,同样的,原序列的操作也会反应在子序列上。由此我们可以推出,sub1的修改也会影响到sub2,但是,当sub1的修改操作是在sub2已经建立之后的,就会引发异常。
可以想象,sub2的内容可能不知道在某个地方就被修改了!因此必须要避免这种情况的发生。
解决方案
解决方案也简单,复制一份我们需要的元素不就可以了吗?
public static void testSubList() {
List<String> origin = new ArrayList<>();
origin.add("1");
origin.add("2");
origin.add("4");
origin.add("5");
List<String> sub1 = origin.subList(0, 2);
List<String> newSub1 = new ArrayList<>(sub1);
newSub1.add("3");
List<String> sub2 = origin.subList(2, origin.size());
List<String> newSub2 = new ArrayList<>(sub2);
System.out.println(newSub1); //[1, 2, 3]
System.out.println(newSub2); //[4, 5]
}
所以在使用subList时一定要注意。