116.数组和List之间的转换总结

  • 数组转列表(List)
  1. for循环遍历数组,再通过List的add()方法添加
        String[] names = new String[]{"name1","name2","name3"};
        ArrayList list = new ArrayList<>();
        for (int i = 0;i< names.length;i++){
            list.add(names[i]);
        }
  1. 使用Arrays.asList(T... a)方法返回固定大小的列表
        String[] names = new String[]{"name1","name2","name3"};
        List nameList = Arrays.asList(names);

这里要注意,这个方法返回的是一个视图,也就是说这里的List的大小是不可以改变的。这是为什么呢?List不是会自动扩容吗?
我们看源码:

    @SafeVarargs 
    @SuppressWarnings("varargs")
    public static  List asList(T... a) {
        return new ArrayList<>(a);
    }

注意这里的ArrayList并不是java.util.ArrayList 而是在Arrays内部定义了一个静态的私有的ArrayList对象,也可以说是java.util.ArrayList 的阉割版,为了防止这个ArrayList被外部修改,Arrays也使用private限制了它的访问权限。

 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) 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 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 c) {
            Arrays.sort(a, c);
        }
    }

重新定义之后的ArrayList貌似只拥有了这些方法,其他的方法都来源于抽象类AbstractList,至于RandomAccess, java.io.Serializable只要声明就好,并没有赋予其他特性。

116.数组和List之间的转换总结_第1张图片
被阉割后的ArrayList

@SafeVarargs注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。 一个方法使用@SafeVarargs注解的前提是,开发人员必须确保这个方法的实现中对泛型类型参数的处理不会引发类型安全问题。
注意: 由于AbstractList中并没有对add和remove进行实现,

  public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
 public E remove(int index) {
        throw new UnsupportedOperationException();
    }

所以

        String[] names = new String[]{"name1","name2","name3"};
        List nameList = Arrays.asList(names);

        nameList.set(0,"name0");
        System.out.println(nameList.get(0));

        nameList.add("name4");

这段代码在编译期不会报错,但是运行时实际上是执行了AbstractList中的方法,然后throw new UnsupportedOperationException()会抛异常,这在逻辑上是解释的通的,再看运行结果:

name0
Exception in thread "main" java.lang.UnsupportedOperationException
name000
--------------------
    at java.util.AbstractList.add(AbstractList.java:148)
    at java.util.AbstractList.add(AbstractList.java:108)
    at com.alibaba.dubbo.service.impl.Student.main(Student.java:121)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

为什么对names的修改会影响到nameList?对nameList的set也会影响到names呢?
回到源码看看ArrayList的构造函数:

 ArrayList(E[] array) {
       a = Objects.requireNonNull(array); 
}

然后再看Objects的requireNonNull

public static  T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
 }

这里requireNonNull只是做了简单的判空处理,然后就把传进来的数组引用直接返回给了ArrayList的内部的一个泛型类型的数组常量。我们知道数组是引用传递,也就是说ArrayList内部指向了原数组,就相当于数组本质没有变,只是给它加了层衣服,这就是官方文档上强调过的只是返回了一个视图列表。开发时要尤其注意。当然,我们可以通过传入一个数组的深复制的实例进行创建来解决:

        ArrayList list = new ArrayList<>(Arrays.asList(names.clone()));
        list.add("ggr");
        System.out.println(list);
  • 列表转数组(List)
  1. for循环(底层数据结构为数组)/foreach(底层数据结构为链表)遍历赋值给数组
    public static void main(String[] args) {

        String[] nameB = new String[]{"name1","name2","name3","name4","name5","name6"};

        ArrayList listB = new ArrayList(Arrays.asList(nameB.clone()));
        LinkedList listA = new LinkedList<>(Arrays.asList(nameB.clone()));

        String[] listToArrA = new String[listA.size()];
        String[] listToArrB = new String[listB.size()];
        for(int i=0;i
  1. 使用Collection.toArray() 方法转换为一个数组
        String[] nameB = new String[]{"name1","name2","name3","name4","name5","name6"};

        ArrayList listB = new ArrayList(Arrays.asList(nameB.clone()));
        LinkedList listA = new LinkedList<>(Arrays.asList(nameB.clone()));

        String[] listToArrA = new String[listA.size()];
        String[] listToArrB = new String[listB.size()];
        listToArrB = listB.toArray(listToArrB);
        listToArrA = listA.toArray(listToArrA);

这里要注意我们使用的是这个 public T[] toArray(T[] a)有参的方法,不是public Object[] toArray()方法这个是有很大区别的。
如果使用了public Object[] toArray()这个方法我们的返回值是一个Object[],而对于 public T[] toArray(T[] a)它会帮我们做自动类型转换的。所以要万分小心,别用错了。
我们这里看下它们的源码:

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

回调public static T[] copyOf(T[] original, int newLength)方法

 public static  T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
  }

由于我们传入的时候用的就是Object类型的数组,所以返回的当然也是一个Object数组咯!

再看public T[] toArray(T[] a)方法的源码实现:

   public  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());
        //长度相同时,采用深复制直接copy
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)//超出数组的部分直接舍弃
            a[size] = null;
        return a;
    }

我们再看看public static T[] copyOf(U[] original, int newLength, Class newType)的源码

   public static  T[] copyOf(U[] original, int newLength, Class 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;

这个方法内部使用反射在创建临时数组的时候给定了类型,所以我们这个方法返回的数组都是带了类型的。

备注: System.arraycopy底层是一个拷贝。我们可以通过如下代码测试:

        public static void main(String[] args) {

        String[] nameB = new String[]{"name1","name2","name3"};
        Object[] nameC = new String[3];
        System.arraycopy(nameB,0,nameC,0,nameB.length);

        nameC[0]="ggr";
        nameC[1]="ggr";
        nameC[2]="ggr";
        System.out.println(nameB[1]);
        System.out.println(nameC[1].getClass());
    }

运行结果:

name2
class java.lang.String

第一个name2说明System.arraycopy方法是一个深复制的方法。

你可能感兴趣的:(116.数组和List之间的转换总结)