利用Jdk 6260652 Bug解析Arrays.asList

在java.util.ArrayList源碼中:java

c.toArray might (incorrectly) not return Object[] (see 6260652) 產生疑惑:數組

附上Java Bug 網址: Java Bug Databasedom


,能夠根據關鍵詞或bug id 查詢詳細信息

這個Bug的描述中能夠看出:
緣由:Arrays內部實現的ArrayList的toArray()方法的行爲與規範不一致。
代碼測試:
import java.util.*;

public class Test{
     
    public static void demo1(){
        System.out.println("this is demo1");
        List<String> list=new ArrayList<>();
        list.add("張三");
        list.add("王五");
        Object[] arr=list.toArray();
        System.out.println(arr.getClass().getCanonicalName());
        arr[0]=new Object();
        Test.printArr(arr);
        
        /*
        正常編譯、執行:
        this is demo1
        java.lang.Object[]
        java.lang.Object@15db9742  王五
        */
        
    }
    
    
    public static void demo2(){
        System.out.println("this is demo2");
        List<String> list = Arrays.asList("張三", "王五");
        
        Object[] arr=list.toArray();
        System.out.println(arr.getClass().getCanonicalName());
        arr[0]=new Object();
        Test.printArr(arr);
        
        /*
        正常編譯
        執行輸出:
        this is demo2
        java.lang.String[]
        Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object
        at Test.demo2(Test.java:31)
        at Test.main(Test.java:55)
        */
        
    }
    
    public static void demo3() {
        System.out.println("this is demo3");
        Object[] arr = new String[]{"張三", "王五"};
        System.out.println(arr.getClass().getCanonicalName());
        arr[0] = 7;
        Test.printArr(arr);
        
        /*
        正常編譯
        執行輸出:
        this is demo3
        java.lang.String[]
        Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
        at Test.demo3(Test.java:48)
        at Test.main(Test.java:71)
        */
    }
    
    public static void printArr(Object[] arr) {
        for (Object o : arr) {
            System.out.print(o + "  ");
        }
        System.out.println();
    }
    
    public static void main(String[]args){
        
        //Test.demo1();
        //Test.demo2();
        Test.demo3();
    }
}
輸出截圖:



分析過程詳解:
第一步:
看ArrayList帶Collection對象的構造函數源碼(java.util.ArrayList):
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);
 }
看java.util.ArrayList,中toArray()源碼:
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    /**
     * 返回 ArrayList 元素組成的數組
     * @param a 須要存儲 list 中元素的數組
     * 若 a.length >= list.size,則將 list 中的元素按順序存入 a 中,而後 a[list.size] = null, a[list.size + 1] 及其後的元素依舊是 a 的元素
     * 不然,將返回包含list 全部元素且數組長度等於 list 中元素個數的數組
     * 注意:若 a 中原本存儲有元素,則 a 會被 list 的元素覆蓋,且 a[list.size] = null
     * @return
     * @throws ArrayStoreException 當 a.getClass() != list 中存儲元素的類型時
     * @throws NullPointerException 當 a 爲 null 時
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        // 若數組a的大小 < ArrayList的元素個數,則新建一個T[]數組,
        // 數組大小是"ArrayList的元素個數",並將「ArrayList」所有拷貝到新數組中
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        // 若數組a的大小 >= ArrayList的元素個數,則將ArrayList的所有元素都拷貝到數組a中。
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

能夠看出,因爲ArrayList中elementData類型爲Object[],因此調用copyOf()返回值類型爲Object[]。函數

第二步:測試

看 Arrays.asList()源碼:this

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

仔細閱讀官方文檔,你會發現對 asList 方法的描述中有這樣一句話:spa

返回一個由指定數組生成的固定大小的 List。3d

注意:參數類型是 T ,根據官方文檔的描述,T 是數組元素的 class。code

任何類型的對象都有一個 class 屬性,這個屬性表明了這個類型自己。原生數據類型,好比 int,short,long等,是沒有這個屬性的,具備 class 屬性的是它們所對應的包裝類 Integer,Short,Long。對象

asList 方法的參數必須是對象或者對象數組,而原生數據類型不是對象。當傳入一個原生數據類型數組時,asList 的真正獲得的參數就不是數組中的元素,而是數組對象自己。(解決方案:使用包裝類數組。)

繼續分析:

此時的ArrayList並不是咱們經常使用的java.util.ArrayList,而是Arrays的內部類。它繼承自AbstractList,天然實現了Collection接口,代碼以下:

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) {
            if (array==null)
                throw new NullPointerException();
            a = array;
        }

        public int size() {
            return a.length;
        }
        。。。。。。
 }
能夠發現, 這裏的a不是 Object[],而是E[]
a稱爲該ArrayList的backed array。同時構造函數也是直接用array給a賦值。這就是問題的所在。
另外,這個內部類裏面並無add,remove方法,它繼承的AbstractList類裏面有這些方法:
ublic abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    
。。。。。。。
public void add(int index, E element) { throw new UnsupportedOperationException(); } public E remove(int index) { throw new UnsupportedOperationException(); } 。。。。。。

abstractList這個抽象類所定義的add和remove方法,僅僅是拋出了一個異常!

若是是想將一個數組轉化成一個列表並作增長刪除操做的話,建議代碼以下:

public class Test {
   public static void main(String[] args) {
      String[] myArray = { "張三", "李四", "趙六" };
      List<String> myList = new ArrayList<String>(Arrays.asList(myArray));
      myList.add("王五");
   }
};  

demo2(測試代碼中的):

public static void demo2(){
        System.out.println("this is demo2");
        List<String> list = Arrays.asList("張三", "王五");
        
        Object[] arr=list.toArray();
        System.out.println(arr.getClass().getCanonicalName());
        arr[0]=new Object();
        Test.printArr(arr);
        
        /*
        正常編譯
        執行輸出:
        this is demo2
        java.lang.String[]
        Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object
        at Test.demo2(Test.java:31)
        at Test.main(Test.java:55)
        */
        
    }

上面的拋出異常分析:

asList方法直接將String[]數組做爲參數傳遞給ArrayList的構造方法,而後將String[]直接賦值給內部的a,因此a的真實類型是String[],根據JLS規範String[]的clone方法返回的也是String[]類型。最終,toArray()方法返回的真實類型是String[],此時,操做arr[0]=new Object();是向數組中添加Object對象,就會報異常的問題了。

Jdk 6260652 Bug 問題是在2005年提出的,如今已經解決了,使用toArray(T[] a)避免Exception的發生,因此可能會致使類型不匹配的錯誤。

 小總結:

Arrays.asList()的使用方法:

該方法是將數組轉化爲list。有如下幾點須要注意:

1.該方法不適用於基本數據類型(byte,short,int,long,float,double,boolean)

解決方案:使用包裝類數組,例子以下:

public class Test {
   public static void main(String[] args) {
      Integer[] myArray = { 1, 2, 3 };
      List myList = Arrays.asList(myArray);
      System.out.println(myList.size());
   }
}

2.該方法將數組與列表連接起來,當更新其中之一時,另外一個自動更新

3.不支持add和remove方法

將數組轉化爲一個List對象,通常會想到Arrays.asList()方法,這個方法會返回一個ArrayList類型的對象。可是用這個對象對列表進行添加刪除更新操做,就會報UnsupportedOperationException異常。

緣由:這個ArrayList類並不是java.util.ArrayList類,而是Arrays類的靜態內部類!

說明:asList的返回對象是一個Arrays內部類,並無實現集合的修改方法。Arrays.asList體現的是適配器模式,只是轉換接口,後臺的數據還是數組。

String[] str = new String[]{"張三","王五"};
List list = Arrays.asList(str);

第一種狀況:list.add("趙四"); //運行時異常

第二種狀況:str[0] = "大二哈"; //list.get(0)也隨着修改。

此類包含用來操做數組(好比排序和搜索)的各類方法。此類還包含一個容許將數組做爲列表來查看的靜態工廠。 除非特別註明,不然若是指定數組引用爲 null,則此類中的方法都會拋出 NullPointerException。

相關文章
相關標籤/搜索