Arrays.asList()源码探析
今天在看《Java编程思想》中关于容器的那一章,其中提到了如何使用Arrays.asList向Collection中添加一组元素,Arrays是位于java.util包中的一个工具类,这个工具类主要包含了各种操作数组的方法,而asList方法是用来将一个数组或者一个用逗号分隔的元素列表(使用的可变参数)转化为一个List对象。其官方的介绍如下:
* 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}. * * <p>This method also provides a convenient way to create a fixed-size * list initialized to contain several elements: * <pre> * List<String> stooges = Arrays.asList("Larry", "Moe", "Curly"); * </pre> * * @param <T> 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
汉语意思大致是:
通过指定数组提供的元素返回一个固定大小的list。
但是这里面有一句很重要的话:"@return a list view of the specified array",意思是返回一个基于指定数组的List视图,所以它在底层实现的时候是不需要使用多余的内存来创建新的List以及复制操作的。
我们再来看括弧里的这句话:"Changes to the returned list "write through" to the array.",汉语意思是对返回列表的修改会直接写入到数组里,这不就是符合视图的特性么。
"This method acts as bridge between array-based and collection-based APIs",这个方法的作用就是作为基于数组的和基于Collection之间的桥梁,这样就可以方便调用一些Collection的API。
同时这个方法还可以通过可变参数来返回一个List视图,而这个可变参数是泛型化的。
其实现源码如下:
@SafeVarargs @SuppressWarnings("varargs") public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
可以看到这个方法的参数就是一个泛型化的可变参数,该方法返回的是一个ArrayList<>(a)对象,这个ArrayList可不是java.util包中的那个,而是Arrays类下的一个静态内部类,也就是java.util.Arrays.ArrayList,所以我们再操作这个对象的时候不能想当然的像操作java.util.ArrayList对象一样。我们来对比一下这两个ArrayList:
1、java.util.ArrayList
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{……}
2、java.util.Arrays.ArrayList
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable{……}
可以看到二者都继承了AbstractList这个抽象类,
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>{……}
AbstractList实现了这个接口List,也就是说java.util.Arrays.ArrayList类间接的并没有直接实现List接口,它只是实现了一部分List接口中的方法,而那些没有实现方法的则是直接继承自AbstractList类,我们来看一下AbstractList类对于List中的add方法是如何重写的:
// AbstractList
public boolean add(E e) { add(size(), e); return true; } public abstract int size(); public void add(int index, E element) { throw new UnsupportedOperationException();
} //java.util.Arrays.ArrayList重写的size方法 @Override public int size() { return a.length; }
可以看出来,AbstractList重写的add方法,如果它的子类对象想调用add方法添加元素时,会去执行重载的add方法,而这个重载的方法会直接抛出一个UnsupportedOperationException的异常,所以对于java.util.Arrays.ArrayList类型的对象是不能向其中添加元素的,当然也不能移除元素,说白了就是不能修改这个底层数组的大小,因为一开始就说了,asList方法返回的只是一个List视图,其底层还是一个数组,数组的大小是不能改变的。
在AbstractList类中,get、add、remove、set方法返回的都是上面那个不支持异常,但是java.util.Arrays.ArrayList类重写了get、set方法:
@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; }
所以我们是可以修改这个java.util.Arrays.ArrayList对象中的值的,而对List视图值的修改最终都是其对底层数组的修改。其中,再来看:
private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); }
java.util.Arrays.ArrayList的构造方法用于接收传过来的数组参数的变量是一个经过final的数组,也就是这个一旦a被final修饰了,那么它在程序运行始终都是指向同一个地址的,这个地址一直都不会发生改变,final限定了a这个变量,而经过构造方法的初始化之后,a指向的数组的大小也就固定了,因为数组a的首地址固定了,也就不可能再因为要添加新的元素而此时空间不够再去开辟新的空间了从而指向新的地址了。虽然a的值不能改变了,但是它指向的数组中的值是可以改变的。
java.util.ArrayList则是直接实现了List这个接口,它实现了List中的所有方法,二者java.util.ArrayList对象是可变长的可以对其中的元素进行添加删除。
今天就先写到这里,以后会专门对java.util.ArrayList的源码进行一个详细的研究。