借助可变参数一次创建包含多个元素的 ArrayList

一、开门见山

两种可以直接把要添加元素写在参数列表里的方法:
第一种:Arrays.asList 方式

List<Integer> l1 = new ArrayList<>(Arrays.asList(1, 2, 3));

第二种:Collections.addAll 方式

List<Integer> l2 = new ArrayList<>();
Collections.addAll(l2, 1, 2, 3);

二、抽丝剥茧

ArrayList 自身的构造函数和两个 addAll 方法都不支持可变参数,
public boolean addAll(Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c)

2.1 Arrays.asList 方式

Arrays.asList 实现:

	// asList 方法
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
	
	// Arrays 内部静态类 ArrayList,和 java.util 包下的 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);
        }
        
        @Override
        public int size() {
            return a.length;
        }
        
        @Override
        public Object[] toArray() { // toArray 方法借助 Object 的 clone 方法实现
            return a.clone();
        }
        
        // ...
    }

这里的 ArrayList 是 Arrays 的静态内部类,最大的特点是不能增删元素。
以 asList 可变参数数组为底层存储结构。
其中 toArray 方法通过 Object 的 clone 方法实现:

protected native Object clone() throws CloneNotSupportedException;

ArrayList 参数为 Collection 的构造函数:

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class); // 会执行到这儿
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

Arrays.copyOf 方法:

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

Arrays.copyOf()方法详解-jdk1.8
大致要点:
newType 的类型是 Class<? extends T[]>,T 是一个泛型,如果与 Object 做比较的话,父子关系肯定是 Object >= T 的,由于 == 只能在同类型中才能做比较,所以需要强制转换。
class 对象是单例模式,所以可以用 == 比较。
方法目的:数组向上转型并拷贝


总结前面的代码,待添加元素经过两次拷贝后,添加成功。

  • 第一次拷贝发生在,ArrayList 构造函数的第一句 elementData = c.toArray();,通过 Object 的 clone 方法进行了一次拷贝。
  • 第二次拷贝还是发生在 ArrayList 构造函数,elementData = Arrays.copyOf(elementData, size, Object[].class);

解释一下为什么 elementData.getClass() != Object[].class 为 true:
因为执行完 Arrays.asList(1, 2, 3) 后,底层的数组类型为 Integer[]。
所以下面这段代码的执行结果为 true,true。

import java.util.Arrays;

public class Test {
	public static void main(String[] args){
		System.out.println(Arrays.asList(1, 2, 3).toArray().getClass() == Integer[].class);
		// 显式类型声明
		System.out.println(Arrays.<Object>asList(1, 2, 3).toArray().getClass() == Object[].class);
	}
}

2.2 Collections.addAll 方式

    public static <T> boolean addAll(Collection<? super T> c, T... elements) {
        boolean result = false;
        for (T element : elements)
            result |= c.add(element);
        return result;
    }
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

三、结论

初始元素少的话,直接写在可变参数里,喜欢用哪种就用哪种,几个元素的添加不会有什么效率问题。
初始元素多的话,比如添加一个大数组,推荐使用 Arrays.asList 方式,本地方法拷贝会快得多,尽管拷贝两次。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章