ArrayList的克隆与toArray

概述

列表(list)是一款即实用又经常使用的数据结构,用来存储线性结构的数据。在JDK中对List的支持主要有两种,也是最经常使用的两种。一种是ArrayList,一种是LinkedListjava

并且这两种list的区别也常常出如今节操公司的面试题中。节操高一点可能还会问某种list的具体实现,下面说说这两种List的区别。本文主要分析ArrayList的源码。node

  • ArrayList

ArrayList的底层主要是基于数组来实现的。
众所周知,数组是有get√到RandomAccess技能的,也就是说,array[index]这个操做的时间复杂度为O(1)。
可是,在数组上进行增删操做,就须要比较费力了,往数组中插入一项,须要将其后面的全部项都向后移动一个单元,删除数组的一项,则须要把后面的全部项都向前移动一位。
最好的状况下,你要插入或者删除的那一项恰好位于数组的末端,那么就无需再移动任何数据。
因此,若是增删操做比较多,则不该该选用基于数组实现的ArrayList。面试

  • LinkedList

LinkedList的底层主要是基于链表来实现的。
链表的优点就是增删操做不须要像ArrayList同样拷贝数组(移动意味着拷贝),只须要更改先后节点的引用。菊葛丽子,节点A和节点B中间须要插入一个节点C。一开始,A的后继是B,B的前驱是A(不明白前驱后继这两个术语请查找数据结构方面的书),要插入C时,只须要作如下几个操做:数组

  1. 将A的后继改成C
  2. 将C的前驱改成A
  3. 将C的后继改成B
  4. 将B的前驱改成C。

    删除操做同理可推。
    可是,LinkedList要进行随机查找可就麻烦了,必须得先从链表的一端(从头部或者尾部)开始找,我要查找第10个,它必须先找到第1个,而后第2个,... ,第9个,而后才是第十个。所以,LinkedList适合增删操做比较多的场景。
    能够看看JDK源码中LinkedList的访问函数:微信

Node<E> node(int index) {
        // assert isElementIndex(index);
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

深拷贝与浅拷贝

翻开ArrayList的源码,能够看到其中有一个clone()方法,用于克隆当前ArrayList实例。
其方法实现以下:数据结构

/**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            @SuppressWarnings("unchecked")
                ArrayList<E> v = (ArrayList<E>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }

能够看到,先调用了super.clone()方法复制了一个全新的对象,并把它赋值给v。因为java.lang.Object.clone()只是一种浅复制,因此,v的elementData引用的仍是当前ArrayList的elementData的引用,这就意味着,在v上作操做,会影响原来的ArrayList的值。这也是为何须要再跟上这么一个语句的缘由v.elementData = Arrays.copyOf(elementData, size);。这句话的做用是对原来ArrayList的elementData进行一个数组拷贝,而后赋值给v的elementData,这样,v的elementData就是原先ArrayList的elementData的一个副本,再也不是内存中同一块数组。在v上的操做也不会影响到原先的ArrayList。这才是ArrayList的clone()方法真正想作的事情。app

toArray的坑

在ArrayList的构造函数中,能够看到有这样一个构造函数:less

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

这个构造函数用另一个集合中的元素来构造ArrayList。
可是,注意到其中一句注释
c.toArray might (incorrectly) not return Object[] (see 6260652)
说c的toArray()方法有可能不返回类型为Object[]的数组。而且,在jdk的bug列表中编号为6260652的bug能够找到解释:dom

A DESCRIPTION OF THE PROBLEM :
The Collection documentation claims that collection.toArray()is "identical in function" to collection.toArray(new Object[0]);
However, the implementation of Arrays.asList does not follow this: If created with an array of a subtype (e.g. String[]), its toArray() will return an array of the same type (because it use clone()) instead of an Object[].
If one later tries to store non-Strings (or whatever) in that array, an ArrayStoreException is thrown.
Either the Arrays.asList() implementation (which may return an array of component type not Object) or the Collection toArray documentation (which does not allow argumentless toArray() to return arrays of subtypes of Object) is wrong.

在Java中,数组是不具有有兼容性的,举个例子:ide

/**
     *
     *
     * @author Bean
     * @date 2015年7月15日 下午5:14:35
     * @version 1.0
     *
     */
    public class Box {
    
        public static void main(String[] args) {
            Object[] obj1 = new Object[1];
            obj1[0] = new Student();
            obj1[0] = new Person();
            
            Object[] obj2 = new Person[1];
            obj2[0] = new Person();
            obj2[0] = new Student();
            
            Object[] obj3 = new String[1];
            obj3[0] = new Person(); 
        }
    }
    
    class Person {
    }
    
    class Student extends Person {
    }

在main方法的最后一行将会抛出一个异常

Exception in thread "main" java.lang.ArrayStoreException: cn.com.beansoft.box.Person
    at cn.com.beansoft.box.Box.main(Box.java:34)

在Collection的API中,声称toArray()与toArray(new Object[0])方法是等同的。可是Arrays.asList返回的List却没有这样的等同关系。其实在Arrays类中也有另一个ArrayList类,只不过它是个内部类,Arrays.asList就是返回的这个内部的ArrayList。源码以下所示:

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

而这个内部的ArrayList,其toArray方法是调用clone来实现的。以下:

public Object[] toArray() {
        return a.clone();
   }

因此,若是Arrays.asList(T... array)里面的T不是Object类型,而是String类型。那么其toArray方法返回的只是一个String[]类型的数组,而不是Object[]类型。

java.util.ArrayList这个类,原本就是个泛型类。若是它底层的数组类型不是Object[]类型,那么实现泛型存储就是一件没法实现的事了。

上面所说的Java中数组是类型不兼容的,说白了就是在Java中咱们能够这样作:

Object o = new Person();
    o = new String();

可是不能这样作:

Object[] obj3 = new String[1];
    obj3[0] = new Person();

扫一扫关注个人微信公众号

相关文章
相关标签/搜索