谨慎使用Arrays.asList()

2023年6月28日12:07:20

经过了两个月的【深入学习Java编程方法】课程学习,对Java的一些新特性有了更多的了解。依旧,坑洼遍地。

Java中的Collection类是一个很好用、很常用的类,省去了当年C语言手动链表的麻烦之后,感觉整个人都变懒了。有些时候我们需要将某个或者某几个元素作为Collection类来进行传参之类的操作的时候,new一个新List或者Set再挨个add就感觉十分麻烦。从老师的PPT中学到了一招Arrays.asList(T... a)之后,感觉传参舒爽了很多。但是,在经历了一系列艰苦卓绝的Debug之后,终于找到了这个舒适的方法背后的阴险之处。

先说说从别人那里得到的一些结论:

  1. 不支持add和remove方法
  2. 长度不可变
  3. 该方法将数组与列表链接起来,当更新其中之一时,另一个自动更新
  4. 对基本类型数组并不十分友好

前两条基本是一个意思,因此就一起讨论。查看一下asList的实现:

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

看起来好像没什么问题,但是查看了import之后才发现,这个ArrayList不是java.util包下的,而是java.util.Arrays.ArrayList!意思就是,它返回的是Arrays自身的一个内部类,只是和util的那个重名了。这个类的成员变量E[] a是final类型,类本身的内部充满了各种各样的Observer,唯一的一个set(int index, E element)是需要一对一进行替换的方法。因此,这个类内部并没有实现Collection所拥有的add和remove方法。由于成员变量是一个final类型的数组,因此其长度也是不可变的。可以初步认为,asList生成的List给了用户一个【我真的创建了一个Collection】的错觉,实际上这个方法返回的List的本质还是一个数组,并且长度与调用时传入的元素个数相同,不可加长也不可缩短。这种欺骗消费者的行为着实不厚道。

这种结构导致某些情境下本来对List可行的操作出错。这里是我遇到的一种情况。

public class test {
		Map<String, List<Integer>> map = new HashMap<>();
		String test = "test";
		int test1 = 1;
		int test2 = 2;
		map.put(test, Arrays.asList(test1, test2));
		List<Integer> takeout = map.get(test);
		int test3 = 3;
		takeout.add(test3); // 运行到这里报错
	}
}

我使用asList向map中传入了一个List,然后我将其取出,进行List的add操作,运行时则会报错:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at test.test.main(test.java:48) // 对应 takeout.add(test3) 这行

也就是说,即使我定义这个列表的类型是List,我把它放进Map中再拿出来,还是会导致无法及进行正常的add操作。remove操作同理。

知道了这一点,以后如果创建的List仅需要在其基础上进行遍历的话,asList是可以的。但是如果涉及到修改List的元素的话,请不要使用这个方法创建List,因为他tnn的就是一个数组!

常见异常:java.lang.UnsupportedOperationException。

接下来看看第三条。asList的参数可以是一个数组,传进去数组的话相当于进行了一个数组与Collection的连接。但是,这两者是“同生共死”的关系,修改了其中的哪一个,另一个都会发生相应的变化。看个例子:

public class aslist {
	public static void main(String[] args) {
		Integer[] a_Integer = new Integer[] { 1, 2, 3, 4 };
		List a_Integer_List = Arrays.asList(a_Integer);
                foreach(a_Integer_List);

                a_Integer_List.set(0, 0);
		foreach(a_Integer_List);
		foreach(a_Integer);

		a_Integer[0] = 5;
		foreach(a_Integer_List);
		foreach(a_Integer);
	}
    /* 打印方法 */
    private static void foreach(List list) {
        for (Object object : list) {
            System.out.println(object + " ");
        }
        System.out.println();

    }

    private static void foreach(int[] a_int) {
        for (int i : a_int) {
            System.out.println(i + " ");
        }
        System.out.println();
    }

运行结果:

1 
2 
3 
4 

0 
2 
3 
4 

0 
2 
3 
4 

5 
2 
3 
4 

5 
2 
3 
4 

可以看到,当修改数组a_Integer的时候,a_Integer_List中下标相同的元素发生了相同的改变;反之用set方法修改a_Integer_List亦然。这是一个需要注意的地方,使用时要额外注意两者同步修改可能发生的异常。

最后一条,在介绍第三条的时候可能有人发现了,案例中创建的数组是Integer类型而非int类型的。如果我们这样调用asList方法:

                int[] a_int = { 1, 2, 3, 4 };
                List a_int_List = Arrays.asList(a_int);
		foreach(a_int_List);

输出结果为:[I@15db9742

(实机操作视控制台显示真实值为准)

这里的asList并没有将a_int视作一个int类型的数组,而是将这个数组本身当成了一个元素,创建成了一个以数组为元素的List。并且,在这种情况下,是无法调用set方法来同步修改数组的值的。因此,使用基本数据类型的时候,需要创建它的包装类的数组,再调用asList才能尽可能遇到少的错误。

常见异常:java.lang.ArrayStoreException。

到此,我在实验中以及网络中获取的关于asList的陷阱介绍就告一段落。

稍微总结一点的话,如果在实际使用过程中,需要创建或者传递一个只需要进行遍历操作的Collection的话,Arrays.asList()是一个十分方便的方法,可以节省很多对于列表的操作。但是如果需要在这个结构上进行添加、删除元素的操作的话,就不要使用这用方式创建List、Set等了。

所以说,不该偷懒的时候还是要逼一下自己的(¬_¬)

部分文字及代码来源:

https://blog.csdn.net/cntanghai/article/details/7188296

https://blog.csdn.net/keketrtr/article/details/47108435

http://www.cnblogs.com/Miracle-Maker/p/6360069.html

  • 作者:EvaneScencE2534
  • 原文链接:https://blog.csdn.net/m0_37871303/article/details/79995243
    更新时间:2023年6月28日12:07:20 ,共 3189 字。