今天在學習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$ArrayList
的toArray()
的實現方式:ui
java.util.ArrayList
spa
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
中將會調用Arrays
的copyOf()
方法,傳入一個newType
進行類型判斷,newType
的值爲original.getClass()
,因爲original.getClass()
返回的類型爲Object[]
(具體看ArrayList
的源碼可知),因此調用toArray()
以後將返回一個Object[]
類型數組,因此往listArray
變量裏邊丟一個Object
類型的對象固然不會報錯。
再看看java.util.Arrays$ArrayList
的實現,能夠看出數據類型定義爲泛型E
,E
的具體類型將根據你傳入的實際類型決定,因爲你傳入了"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
處卻報錯了,由於1
處fa
變量的實際類型是Son
,引用類型爲Father
,向下轉換取決於實際類型而不取決於引用類型,好比fafa
這個變量的實際類型就是其自己Father
,在java中,父類默認是不能強制轉換爲子類的。
首先最重要有如下幾點:
一、Java中數組集合向上轉型以後,不能往數組集合中添加引用類型(即父類型)的對象,而應該添加實際類型的對象,好比說``Father[] father = son[],你就不能往
father中添加
Father類型了,而應該是
Son`
二、Java中向上轉型是默認容許的,可是向下轉型可能會拋出錯誤,得當心使用!
三、要當心採用Arrays.asList()
建立的集合類型不是java.util.ArrayList
,而是java.util.Arrays$ArrayList
,兩個類的不少方法實現方式也不同。
謝謝閱讀,歡迎評論區交流!