ArrayList 其實也有雙胞胎,但區別仍是挺大的!

1、問題產生

今天在學習ArrayList源碼的時候發現了這麼一句註釋,即:java

c.toArray might (incorrectly) not return Object[] (see 6260652)數組

這句話的意思是Collection集合類型的toArray()方法雖然聲明返回值類型是Object[],可是具體調用時還真不必定就返回Onject[]類型,也有多是其餘的類型,這還要取決於你c的實際類型,使用不當還會拋出異常。這樣講可能會很懵比,下面我將會詳細講解到底爲何,如今咱們先來看看Collection中的toArray()聲明,讓你對這個方法先有個大概的印象。bash

public Object[] toArray(); // 聲明返回值類型爲Object[]
複製代碼

那麼什麼狀況會出現上面的bug呢?咱們先來看看下面兩個例子:ide

一、沒有拋異常的狀況工具

// 聲明一個ArrayList集合,泛型爲String類型
List<String> list = new ArrayList<>();
// 添加一個元素
list.add("list");
// 將上面的集合轉換爲對象數組
Object[] listArray = list.toArray(); ................ 1
// 輸出listArray的類型,輸出class [Ljava.lang.Object;
System.out.println(listArray.getClass());
// 往listArray賦值一個Onject類型的對象
listArray[0] = new Object();
複製代碼

二、拋異常的狀況學習

// 同一建立一個列表,可是如今是經過Arrays工具類來建立,建立的列表類型爲Arrays的內部類ArrayList類型
List<String> asList = Arrays.asList("string");
// 轉換爲對象數組
Object[] asListArray = asList.toArray();.............. 2
// 輸出轉換後元素類型,將輸出class [Ljava.lang.String;
System.out.println(asListArray.getClass());
// 往對象數組中添加Object類型對象,會報錯java.lang.ArrayStoreException
asListArray[0] = new Object();
複製代碼

上面第一種狀況是經過new ArrayList()方式建立的java.util.ArrayList類型,第二種方式是使用Arrays.asList()方式建立的java.util.Arrays$ArrayList的類型,兩個類型名都是ArrayList,但實現方式確實不一樣的。那爲何會報錯呢?歸根到底就是toArray()這個方法的實現方式不一樣致使的。咱們分別先看下java.util.ArrayList類的toArray()java.util.Arrays$ArrayListtoArray()的實現方式:ui

java.util.ArrayListspa

public Object[] toArray() {
    // 調用Arrays工具類進行數組拷貝
    return Arrays.copyOf(elementData, size);...............1
}
複製代碼

Arrays.copyOf()code

public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());.................2
}
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;
}
複製代碼

下面是java.util.Arrays$ArrayList的實現對象

private final E[] a;
@Override
public Object[] toArray() {
    return a.clone();
}
複製代碼

從上面能夠看出,在java.util.ArrayList中將會調用ArrayscopyOf()方法,傳入一個newType進行類型判斷,newType的值爲original.getClass(),因爲original.getClass()返回的類型爲Object[](具體看ArrayList的源碼可知),因此調用toArray()以後將返回一個Object[]類型數組,因此往listArray變量裏邊丟一個Object類型的對象固然不會報錯。

再看看java.util.Arrays$ArrayList的實現,能夠看出數據類型定義爲泛型EE的具體類型將根據你傳入的實際類型決定,因爲你傳入了"string",因此實際類型就是String[],固然調用a.clone()以後仍是同樣返回String[]類型,只不過是這裏作了一個向上轉型,將String[]類型轉爲Object[]類型返回罷了,可是注意,雖然返回的引用Object[],但實際的類型仍是String[],當你往一個引用類型和實際類型不匹配的對象中添加元素時,就是報錯。不服?下面舉個栗子:

// 數組strings爲String[]類型
String[] strings = { "a", "b" };
// 向上轉型爲Object[]類型,那麼這個objects就屬於引用類型爲Object[],而實際類型爲String[]
Object[] objects = strings;
// 添加一個Object類型變量,就報錯啦!
objects[0] = new Object();//! java.lang.ArrayStoreException
複製代碼

爲了加深理解,咱們來總結下java中的向上轉型和向下轉型的區別。咱們都知道咱們能夠經過注入Father fa = new Son()的方式進行聲明,僅爲Father類型爲Son類型的父類,即發生向上轉型,向上轉型在java中是自動完成的,不須要進行強制轉換,不會拋出異常。向下轉型分爲兩種狀況,下面結合代碼演示:

// 向上轉型
Father fa = new Son();

Father fafa = new Father();

// 向下轉型(不會報錯)
Son son = (Son) fa;.................1

// 向下轉型,報錯了java.lang.ClassCastException
Son sonson = (Son) fafa;.......................2
複製代碼

能夠發現1處不會報錯,2處卻報錯了,由於1fa變量的實際類型是Son,引用類型爲Father,向下轉換取決於實際類型而不取決於引用類型,好比fafa這個變量的實際類型就是其自己Father,在java中,父類默認是不能強制轉換爲子類的。

2、總結

首先最重要有如下幾點:

  • 一、Java中數組集合向上轉型以後,不能往數組集合中添加引用類型(即父類型)的對象,而應該添加實際類型的對象,好比說``Father[] father = son[],你就不能往father中添加Father類型了,而應該是Son`

  • 二、Java中向上轉型是默認容許的,可是向下轉型可能會拋出錯誤,得當心使用!

  • 三、要當心採用Arrays.asList()建立的集合類型不是java.util.ArrayList,而是java.util.Arrays$ArrayList,兩個類的不少方法實現方式也不同。

謝謝閱讀,歡迎評論區交流!

相關文章
相關標籤/搜索