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();

掃一掃關注個人微信公衆號

相關文章
相關標籤/搜索