Arrays.asList源码解析及常见误区
一、源码解析
首先我们来看看Arrays.asList()的源码
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
@SafeVarargs
注解表示该方法使用了可变参数,并且在类型安全的前提下进行了操作。@SuppressWarnings("varargs")
注解用于抑制关于可变参数的警告。- 方法签名:
public static <T> List<T> asList(T... a)
- 方法功能:该方法接受一个可变参数
a
,并将其转换为一个ArrayList对象,并返回。 return new ArrayList<>(a);
创建一个新的ArrayList对象,并将可变参数a
作为构造方法的参数传入。这样就将可变参数转换为了ArrayList对象。
可以看到源码的作用是将一个可变参数转换为ArrayList对象,方便对其进行操作和访问。
这样看来似乎没有暴露什么问题,难道真的那么简单吗?
二、常见问题
1.基本类型数组作为参数问题
先进行如下测试:
public static void main(String[] args) {
int[] a = {2,3,4,5};
Integer[] b = {2,3,4,5};
List listA = Arrays.asList(a);
List listA1 = Arrays.asList(2,3,4,5);
List listB = Arrays.asList(b);
System.out.println(listA.size());
System.out.println(listA1.size());
System.out.println(listB.size());
System.out.println("ListA元素类型:"+listA.get(0).getClass());
System.out.println("ListA元素:"+Arrays.toString((int[]) listA.get(0)));
System.out.println("ListA1元素类型:"+listA1.get(0).getClass());
System.out.println("ListB元素类型:"+listB.get(0).getClass());
}
结果如下:
我们发现用int类型的数组作为参数为什么输出size是1呢,使用Integer类型size就是4了。们知道基本类型是不能泛型化的,就是说8种基本类型不能作为泛型参数,要想作为泛型参数就要使用其所对应的包装类。为什么listA的Size为什么是1呢?这是因为listA传递的是一个int类型的数组,数组是一个对象,它是可以泛型化的,也就是说例子中是把一个int类型的数组作为了T的类型,所以转换后在List中就只有一个类型为int数组的元素。后边ListA1与ListB也就可以理解了,一个是进行了自动打包,一个是本来就是包装类型。
2.asList()方法返回对象使用add()方法抛出异常
先进行如下测试:
public static void main(String[] args) {
List<String> pets = Arrays.asList("tiger","mice");
pets.add("lion");
}
结果如下:
我们这里可以看到他抛出了一个异常,难道add方法不是List的基本用法吗?我们继续扒一下源码。
原来原来Arrays的asList方法使用的ArrayList类是一个内部定义的类,而不是java.util.ArrayList类。其部分源码如下:
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
@java.io.Serial
private static final long serialVersionUID = -2764017481108945198L;
@SuppressWarnings("serial") // Conditionally serializable
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
....
}
我们可以看到这是一个静态内部类,存储数组元素的a变量是final类型的,由此判断,这个静态内部类是不能做任何内部元素的添加删除操作的!就跟String类一样,String对象存储字符数组的变量也是有final修饰符的。因为一旦增加数组元素,这个数组容量已经定好的容器就无法装载增加的元素了。
内部类里面并没有add,remove方法,我们可以看下这个类继承的AbstractList类里面对这些方法的实现:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
........
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
}
找到异常的来源了,我们使用asList得到的对象add、remove方法直接就是抛出异常。
那么我们如果要想对asList得到的对象使用add、remove方法,该怎么办呢?
List<String> pets = new ArrayList<String>(Arrays.asList("a", "b", "c"));
可以将传入的参数转换为一个ArrayList对象返回。
3.通过Arrays.asList方法将数组转成集合后,使用set方法修改元素,为什么原来的数组的值也会改变?
测试:
public static void main(String[] args){
String[] strings={"A","B","C"};
List<String> stringList=Arrays.asList(strings);
stringList.set(0,"G");
System.out.println(stringList);
System.out.println(strings[0]);
}
结果:
我们可以看到集合的值和原数组的第一个值都被修改了,为什么原数组也会被修改呢?
原因是静态内部类ArrayList的成员变量a使用了final,用于存储集合的数组引用始终被强制指向原有数组。
所以原数组也会被修改。
那么怎么在不修改原数组的基础上修改集合呢?我们同样可以采取将传入的参数转换为一个ArrayList对象返回的方式。
总结:
数组通过Arrays.asList()方法转成的集合是固定的,因为其使用的ArrayList类是一个内部定义的类,不是java.util.ArrayList类,不支持add()、remove()、clear()等会修改list集合元素个数的方法。要想使用它可以将转换的集合作为参数给ArrayList类对象。