赞
踩
由Arrays.asList()
的源码可知,转换后的列表是固定大小
的,这意味着你不能增加或删除元素,但可以修改现有元素(如果它们是可变对象的话)。转换后得到的列表与原始数组共享相同的底层数组
。这意味着对列表的修改会影响到原始数组,反之亦然。
Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array);
// 下面这行代码会抛出 UnsupportedOperationException
list.add(4); // 错误:无法添加元素
Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array);
list.set(0, 99); // 修改列表的第一个元素
System.out.println(array[0]); // 输出 99,因为数组也被修改了
如果你尝试用基本类型数组(如 int[])调用 Arrays.asList(),结果并不是你期望的列表,而是一个包含单个元素(即整个数组本身)的列表。
int[] primitiveArray = {1, 2, 3};
List<int[]> list = Arrays.asList(primitiveArray);
// list 现在是包含一个元素的列表,这个元素是原始数组 primitiveArray
private static void wrong1() {
int[] arr = {1, 2, 3};
List list = Arrays.asList(arr);
log.info("list:{} size:{} class:{}", list, list.size(), list.get(0).getClass());
}
运行结果:
按道理输出结果list.size()也应该等于3才对,实际输出了个很奇怪的结果,我们从上诉“大坑2”可知,这里用基本类型数组调用Arrays.asList()了,所以得到的并不是期望的结果
正确方式:
private static void right1() {
int[] arr1 = {1, 2, 3};
List list1 = Arrays.stream(arr1).boxed().collect(Collectors.toList());
log.info("list:{} size:{} class:{}", list1, list1.size(), list1.get(0).getClass());
Integer[] arr2 = {1, 2, 3};
List list2 = Arrays.asList(arr2);
log.info("list:{} size:{} class:{}", list2, list2.size(), list2.get(0).getClass());
}
运行结果:
private static void wrong2() {
String[] arr = {"1", "2", "3"};
List list = Arrays.asList(arr);
arr[1] = "4";
try {
list.add("5");
} catch (Exception ex) {
ex.printStackTrace();
}
log.info("arr:{} list:{}", Arrays.toString(arr), list);
}
运行结果:
直接报错。由“大坑1”可知,此处直接往转换后的list中,添加数据,所以直接添加失败
正确方式:
private static void right2() {
String[] arr = {"1", "2", "3"};
List list = new ArrayList(Arrays.asList(arr));
arr[1] = "4";
try {
list.add("5");
} catch (Exception ex) {
ex.printStackTrace();
}
log.info("arr:{} list:{}", Arrays.toString(arr), list);
}
运行结果:
subList方法是Java中List接口的一个成员方法,用于从现有的列表中获取一个子列表视图,这个视图包含了原列表中指定范围的元素。具体来说,该方法的签名如下:
public List<E> subList(int fromIndex, int toIndex)
参数说明:
fromIndex
: 子列表的起始位置(包含)。这个索引必须是非负的,并且小于toIndex。toIndex
: 子列表的结束位置(不包含)。这个索引必须是非负的,并且不大于列表的大小。返回值: 返回一个新的列表视图,包含原列表中从fromIndex(包括)到toIndex(不包括)位置的元素。
1)错误方式
private static void wrong() {
List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
List<Integer> subList = list.subList(1, 4);
System.out.println(subList);
subList.remove(1);
System.out.println(list);
//list和subList维护的是同一个列表对象,并且该列表对象维护了一个modCount,
//当modCount和expectedModCount不相等时,抛出ConcurrentModificationException异常
list.add(0); //会修改同一个列表对象的modCount,导致抛出ConcurrentModificationException异常
try {
subList.forEach(System.out::println);
} catch (Exception ex) {
ex.printStackTrace();
}
}
这段代码将会引发 java.util.ConcurrentModificationException 异常。这是因为在这个场景中,当对list进行修改(通过list.add(0))后,再尝试遍历其子列表subList,根据Java的fail-fast机制,集合在迭代过程中检测到modCount被修改,则会抛出此异常以防止并发修改导致的数据不一致性问题。
2)正确方式1:
private static void right1() {
List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
List<Integer> subList = new ArrayList<>(list.subList(1, 4));
System.out.println(subList);
subList.remove(1);
System.out.println(list);
list.add(0);
subList.forEach(System.out::println);
}
通过创建子列表的新实例new ArrayList<>(list.subList(1, 4))避免了这一问题,因为这样操作后,对原列表的修改不会影响到子列表的迭代,从而避免了异常抛出
3)正确方式2:
private static void right2() {
List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
List<Integer> subList = list.stream().skip(1).limit(3).collect(Collectors.toList());
System.out.println(subList);
subList.remove(1);
System.out.println(list);
list.add(0);
subList.forEach(System.out::println);
}
1)错误方式
private static void oom() {
for (int i = 0; i < 100000; i++) {
List<Integer> rawList = IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList());
data.add(rawList.subList(0, 1));
}
}
结果:堆空间不够了,直接宕机了
大量创建大列表
: 在每次循环中,都通过IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList())创建一个含有一百万个元素的Integer列表。这个操作本身非常消耗内存。但是也不至于直接报OOM,这里只是为更好看到效果子列表引用问题
: 虽然每次循环只取这个大列表的前一个元素作为子列表rawList.subList(0, 1)加入到另一个列表data中,但是subList方法返回的是原列表的一个视图(view),这意味着它并不真正复制数据,而是保留了对原列表的引用。因此,即使只是添加了每个大列表的一个小片段到data中,但由于这些子列表仍然引用着它们对应的大型原始列表,导致大量内存无法被垃圾回收,最终引发内存溢出。2)正确方式
private static void oomfix() {
for (int i = 0; i < 100000; i++) {
List<Integer> rawList = IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList());
data.add(new ArrayList<>(rawList.subList(0, 1)));
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。