阿里Java手册剖析-6.9. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。

824 阅读2分钟

阿里Java开发手册剖析:


【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。

toArray()toArray(T[] array),建议使用有参数的。

直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现ClassCastException 错误。

反例demo:


    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("1");
        arrayList.add("2");
        arrayList.add("3");
        arrayList.add("4");
        arrayList.add("5");
        //默认返回的是Object类型的数组
        //Object[] objects = arrayList.toArray();

        String[] objects = (String[]) arrayList.toArray();
    }

运行结果如下:

主要原因还是集合中的数据类型比如String,是作为ArrayList的泛型存在:public class ArrayList<E> 。泛型在运行时会被擦除。所以运行时ArrayList转换成数组时,是不知道其中元素的具体类型,所以只能是Object了。

正确用法:

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("1");
        arrayList.add("2");
        arrayList.add("3");
        arrayList.add("4");
        arrayList.add("5");

        String[] ints = {};
        String[] strings = arrayList.toArray(ints);

        for (String string : strings) {
            System.out.println(string);
        }

    }

输出如下:

1
2
3
4
5

Process finished with exit code 0

为什么传入个带泛型的数组就能得到正确类型呢? 首先数组的泛型在运行时不会被擦除。


 public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }


    @SuppressWarnings("unchecked")
 public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
    
    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;
    }

使用 toArray 带参方法,数组空间大小的 length最好是0

  1. 等于 0,动态创建与 size 相同的数组,性能最好。
  2. 大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。
  3. 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与 2 相同。
  4. 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。

下面是toArray(T[] a)的源码:



 @SuppressWarnings("unchecked")
 public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
    
    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;
    }

a.length<arrayList.size时,直接使用了ArrayList的size,所以此时传入一个0<length<size大小的数组a就是浪费空间。

如果如length==size,那么多线程还是可能会重复情况2.即0<length<size。 如果length>size,就是浪费空间,还需要把a[size]=null。又会有空指针风险。