@[toc]
Arrays.asList,本来是另外一个类,之所以放到ArrayList相关的文章里面一并讨论,是因为这也是我们日常在使用过程中的一个误区,容易将Arrays.asList产生的结果与ArrayList进行等价。
1.问题重现
1.1 int数组转ArrayList问题
一开始,我们最简单的需求就是将一个数组转为list,搜索了很多资料之后,有人告诉你,Arrays.asList是专门解决这个问题的好办法:
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
List list = Arrays.asList(arr);
System.out.println(list);
}
我们以为,结果会输出“1,2,3,4,5”但是这个结果确是:
[[I@1b6d3586]
这个结果让我们始料未及,究竟是哪出了问题呢?于是,对这个list中的内容进行debug:
可以发现,这个list中实际上只有一个内容,就是这个数组。asList并没有如我们想的将所有的数据的元素转换为ArrayList中的一项值。而是直接将数组变成了一个元素项。
1.2 asList之后的增删问题
对于上面章节,所提到的问题,如果不是单单纯的int数组,string数组是能够成功的。假定我们需要对这个string数组转为list之后,对这个ArrayList进行增删操作:
public static void main(String[] args) {
String[] arr1 = {"1","2","3","4","5"};
List list2 = Arrays.asList(arr1);
System.out.println(list2);
list2.add("6");
}
这又产生了一个意想不到的结果:
[1, 2, 3, 4, 5]
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.dhb.ArrayList.test.ArraysAsListTest.main(ArraysAsListTest.java:21)
出现了UnsupportedOperationException异常。
1.3 asList之后修改原数组的问题
如果我们在使用asList的时候,对原始数组进行修改,那么会出现什么结果?
public static void main(String[] args) {
String[] arr1 = {"1","2","3","4","5"};
List list2 = Arrays.asList(arr1);
System.out.println(list2);
arr1[1] = "0";
System.out.println(list2);
}
输出为:
[1, 2, 3, 4, 5]
[1, 0, 3, 4, 5]
可以看到,当我们使用asList之后,再对其原始的数组进行修改,那么之前被转换的List的值也会发生变化。如果我们把这个结果通过参数传递给其他线程,那么可能就会产生很多共享数据导致的奇怪问题。
2.源码分析
2.1 asList方法
我们打开Arrays源码,看看asList方法:
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with {@link Collection#toArray}. The returned list is
* serializable and implements {@link RandomAccess}.
*
* This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
*
* List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
*
*
* @param the class of the objects in the array
* @param a the array by which the list will be backed
* @return a list view of the specified array
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static List asList(T... a) {
return new ArrayList<>(a);
}
可以看到,asList方法实际上是一个泛型方法,支持传入的是泛型对象。前面我们讨论过泛型的擦除原理,泛型只支持对象。那么对于int数组,由于其元素都是int型,那么泛型是不支持的。那么只有数组才是对象,因此就不难理解为什么前面的int数组被转换成了一个只有一个元素的List,这个元素就是数组本身。
对于这个问题,更好的解决办法是,在jdk1.8之后出现了Stream的装箱的方法:
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
List list = Arrays.asList(arr);
System.out.println(list);
List list1 = Arrays.stream(arr).boxed().collect(Collectors.toList());
System.out.println(list1);
}
输出结果:
[[I@1b6d3586]
[1, 2, 3, 4, 5]
这样通过Stream的bixed方法就很好的解决了这个问题。
2.2 ArrayList内部类
在Arrays的asList方法中,虽然new了一个ArrayList,但是需要注意的是,这个ArrayList并非我们一直使用的ArrayList,而是一个位于Arrays中的内部类:
/**
* @serial include
*/
private static class ArrayList extends AbstractList
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
public T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
@Override
public void replaceAll(UnaryOperator operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
@Override
public void sort(Comparator super E> c) {
Arrays.sort(a, c);
}
}
通过这个源码我们可以发现,这个类继承了AbstractList,其所有的get和set操作都是对原有数组进行修改。而且没有add和remove等会修改数组大小的方法。
String[] arr1 = {"1","2","3","4","5"};
List list2 = Arrays.asList(arr1);
System.out.println(list2);
arr1[1] = "0";
System.out.println(list2);
list2.set(1,"7");
System.out.println(list2);
System.out.println(arr1[1]);
执行上述代码,可以看到输出为:
[1, 2, 3, 4, 5]
[1, 0, 3, 4, 5]
[1, 7, 3, 4, 5]
7
生成的ArrayList,实际上是原始数组的视图,这有点类似我们上一篇对ArrayList中subList方法的分析,都是一个视图功能的内部类。其get,set方法只要修改实际上是修改的原始数组,同理,修改原始数组,ArrayList的内容也会发生改变。这也不难理解为什么会出现UnsupportedOperationException异常。因为这个ArrayList本身并没有add和remove等方法。这个方法是通过抽象类实现的,而在抽象类中:
/**
* {@inheritDoc}
*
* This implementation always throws an
* {@code UnsupportedOperationException}.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*
*
This implementation always throws an
* {@code UnsupportedOperationException}.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
throw new UnsupportedOperationException();
}
这两个方法直接调用本来就是会抛出UnsupportedOperationException异常。
所以这也进一步解释了,为什么ArrayList还需要用AbstractList再增加一层继承。这样可以很好的解耦。
2.3 阿里规范
对于上面这个问题,阿里巴巴的开发规范中也有描述:
禁止再对Arrays.asList产生的集合再进行add、remove、clear操作。这会导致UnsupportedOperationException异常。
3.总结
虽然此ArrayList并不是彼ArrayList,但是这也是我们容易混淆的地方,因此,对于Arrays.asList方法。我们需要注意的是:
- 1.非对象数组,基本类型的数组不能使用Arrays.asList转换。如果需要使用,请用Stream中的boxed方法装箱之后再操作。
- 2.asList产生的ArrayList实际上是Arrays的一个新的内部类。并不是我们认为的ArrayList,这也是大多数人容易混淆的地方。二者并不等价。
- 3.asList产生的ArrayList实际上是对原始数组的一个视图,如果ArrayList进行set修改,那么原始数组的值也会改变。反之对原始数组进行修改,那么ArrayList的值也会改变。因此在我们将asList之后的结果再作为变量进行传递时,要特别注意这一点。
- 4.Arrays.asList产生的ArrayList没有实现add、remove、clear方法,如果调用这些方法会出现UnsupportedOperationException异常。
上述是对Arrays.asList的总结,实际上这是很多人在编码的过程中,从来没有考虑过的问题。在面试过程中,实际上可能并不需要聊到HashMap,这ArrayList阶段就有很多人可能阵亡了。