看面试题的时候,里面提到了这个,之前都是常规的使用,把数组转换为List集合,程序每次也都正常的运行,没在意过这个问题。看到这个问题时直接懵了,难道我之前的使用是错误的吗?后来查了资料确实是有点问题,Arrays.asList()还就真就没把数组转换为List集合,源码底层还是一个数组!。
话不多说。直接上代码,结果才是唯一真理。
再简单不过的一个把数组转换为List集合的例子。
public class ArraysTest {
public static void main(String[] args) {
String[] strings = new String[]{"张三","李四","王二","麻子"};
List<String> list = Arrays.asList(strings);
list.add("啥也不会的程序员");
}
}
可以看到程序的第17行报了一个异常,17行就对应着list.add("啥也不会的程序员");
这个方法。出现异常的原因就是调用了add
方法。一开始就说了,底层还是一个数组,而数组的一个重要特点就是,一旦长度确定之后就不可以改变。所以也就导致了,add
方法出现异常。而且不止addss
方法会出现异常,remove
和clear
方法也会出现异常。
现在只是知道了Arrays.asList()
在执行add
等方法的时候会出现异常,但是具体原因是什么还不清楚,而且如果底层是数组的话,怎么又会说把数组转换为List集合呢?
想要知道原理,就要分析源码了。
先看看Arrays.asList()
这个方法的源码是怎么样的
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
可以看到asList这个方法中并没有什么特殊的代码,只不过传入了一个可变参数,然后又创建了一个ArrayList
对象并返回。那好,就继续看ArrayList的源码。
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
// 后面的省略,不是重点
}
可以发现,这个类是一个私有的静态内部类。并且有一个带参构造器,构造器需要传入一个泛型数组,而后这个泛型数组在经过非空判断后赋值给了final修饰的泛型数组a。哦,到了这里就会发现,其实本质还是一个数组,一个泛型数组,只不过在这个数组外面套上一个ArrayList
类的外壳。
到了这里就会知道了,其实本质还是一个数组,可是,知道了是数组了,那么异常又是哪里来的呢?平常使用中又是怎么把它伪装成List集合使用的呢?既然不清楚,那就继续看源码。ArrayList
类没有关于异常的源码,那就看它父类AbstractList
的源码。
AbstractList的部分源码
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
public boolean add(E e) {
add(size(), e);
return true;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
public void clear() {
removeRange(0, size());
}
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}
// 省略...
}
可以看到,add
和remove
方法都抛出了UnsupportedOperationException
异常,这里就是关键,在调用add
和remove
方法时,因为是继承了AbstractList
类,而ArrayList
又没有重写add
和remove
方法,则会调用父类的方法,抛出异常。clear
方法和add
不太一样,clear
方法又调用了removeRange
方法,而removeRange
方法中又执行了it.remove();
方法,然后再经过JDK源码的一些执行,最后会执行到remove
方法上,所以也会抛出一个异常。
到此,为什么会抛出异常的原因知道了,但是还是没弄清楚是怎么伪装成List集合使用的。
如果细心就会发现,AbstractList
实现了List
接口,然后基于Java的多态特性,父类引用指向子类对象,自然而然就被当做了List集合使用。
到此,分析过源码之后,就知道了为什么Arrays.asList()
没有把数组转换为List
集合,为什么在调用add
、remove
和clear
方法时会抛出异常。知道了怎么伪装成为List集合使用的。
那么又如何正确的将数组转换为List集合呢?
1、手动实现
private static<T> List<T> arrayToList(T[] array){
List<T> list = new ArrayList<>();
for (T t:array){
list.add(t);
}
return list;
}
2、最简单的方法
String[] strings = new String[]{"张三","李四","王二","麻子"};
List<String> list1 = new ArrayList<>(Arrays.asList(strings));
3、Java8的Stream
String[] strings = new String[]{"张三","李四","王二","麻子"};
List<String> list = Arrays.stream(strings).collect(Collectors.toList());