Java ArrayList.toArray(T[]) 方法的參數(shù)類型是 T 而不是 E的原因分析
前兩天給同事做 code review,感覺(jué)自己對(duì) Java 的 Generics 掌握得不夠好,便拿出 《Effective Java》1 這本書再看看相關(guān)的章節(jié)。在 Item 24:Eliminate unchecked warnings 這一節(jié)中,作者拿 ArrayList 類中的 public <T> T[] toArray(T[] a) 方法作為例子來(lái)說(shuō)明如何對(duì)變量使用 @SuppressWarnings annotation。
ArrayList 是一個(gè) generic class,它是這樣聲明的:
Javapublic class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
這個(gè)類的 toArray(T[] a) 方法是一個(gè) generic method,它是這樣聲明和實(shí)現(xiàn)的:
@SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
這個(gè)方法實(shí)際上是在 Collection 接口中聲明的。因?yàn)槲覀兘?jīng)常通過(guò) ArrayList 使用它,這里就用 ArrayList 作為例子了。
1 為什么聲明為不同類型?
我的問(wèn)題是:為什么這個(gè)方法使用類型 T,而不使用 ArrayList 的類型 E ? 也就是說(shuō),這個(gè)方法為什么不聲明成這樣:
Javapublic E[] toArray(E[] a);
如果類型相同的話,在編譯期間就可以發(fā)現(xiàn)參數(shù)的類型錯(cuò)誤。如果類型不同,很容易產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。比如下面這段代碼:
//創(chuàng)建一個(gè)類型為 String 的 ArrayList List<String> strList = new ArrayList<String>(); strList.add("abc"); strList.add("xyz"); //將當(dāng)前的 strList 轉(zhuǎn)換成一個(gè) Number 數(shù)組。注意,下面的語(yǔ)句沒(méi)有任何編譯錯(cuò)誤。 Number[] numArray = strList.toArray(new Number[0]);
運(yùn)行上面的代碼, Line 6 會(huì)拋出 java.lang.ArrayStoreException 異常。
如果 toArray 方法使用類型 E 的話,語(yǔ)句2就會(huì)產(chǎn)生編譯錯(cuò)誤。編譯錯(cuò)誤怎么說(shuō)也比運(yùn)行時(shí)錯(cuò)誤親切啊。并且,generics 的主要目的就是為了類型安全,把類型轉(zhuǎn)換錯(cuò)誤(ClassCastException)消滅在編譯期間。這個(gè)方法卻反其道而行之。難道這是一個(gè)大 bug? Java 的 bug 俺碰上過(guò),但這個(gè)地方出 bug 我還是不太敢相信。
上網(wǎng)一查,這個(gè)問(wèn)題早已被討論過(guò)多次了2, 3, 4。
2 可以提高靈活性
這樣的聲明更靈活,可以把當(dāng)前 list 中的元素轉(zhuǎn)換成一個(gè)更一般類型的數(shù)組。比如,當(dāng)前 list 的類型是 Integer,我們可以把它的元素轉(zhuǎn)換成一個(gè) Number 數(shù)組。
List<Integer> intList = new ArrayList<Integer>(); intList.add(1); intList.add(2); Number[] numArray = intList.toArray(new Number[0]);
如果這個(gè)方法聲明成類型 E,上面的代碼就會(huì)有編譯錯(cuò)誤。 看起來(lái),該方法聲明成下面這樣會(huì)更合適:
Javapublic <T super E> T[] toArray(T[] a);
不過(guò), <T super E> 這樣的語(yǔ)法在 Java 中是不存在的。而且即使存在,對(duì)數(shù)組也不起作用。也正是因?yàn)檫@個(gè)原因,在使用這個(gè)方法時(shí),即使 T 是 E 的父類,或 T 跟 E 相同,也不能完全避免 java.lang.ArrayStoreException 異常5, 6, 7 。請(qǐng)看下面兩段代碼。第一段代碼中 T 是 E 的父類,第二段代碼中 T 和 E 一樣。這兩段代碼都會(huì)拋出異常。
代碼一:
List<Integer> intList = new ArrayList<Integer>(); intList.add(1); intList.add(2); Float[] floatArray = new Float[2]; //Float 是 Number 的子類,所以 Float[] 是 Number[] 的子類 Number[] numArray = floatArray; //下面的語(yǔ)句會(huì)拋出 ArrayStoreException 異常 numArray = intList.toArray(numArray);
代碼二:
List<Number> intList = new ArrayList<Number>(); //List 的類型是 Number。但 Number 是抽象類,只能存它的子類的實(shí)例 intList.add(new Integer()); intList.add(new Integer()); Float[] floatArray = new Float[]; //Float 是 Number 的子類,所以 Float[] 是 Number[] 的子類 Number[] numArray = floatArray; //下面的語(yǔ)句會(huì)拋出 ArrayStoreException 異常 numArray = intList.toArray(numArray);
上面的異常都是由這個(gè)事實(shí)造成的:如果 A 是 B 的父類,那么 A[] 是 B[] 的父類。Java 中所有的類都繼承自 Object,Object[] 是所有數(shù)組的父類。
這個(gè)帖子8里舉了個(gè)例子,說(shuō)明即使這個(gè)方法的類型聲明成 E 也不能避免 ArrayStoreException 異常。
該方法的文檔中也提到了這個(gè)異常:
ArrayStoreException if the runtime type of the specified array is not a supertype of the runtime type of every element in this list.
3 可以與 Java 1.5 之前的版本兼容
這個(gè)方法在 Java 引入 Generics 之前(JDK1.5 中引入了 Generics)就出現(xiàn)了9。那時(shí)它被聲明稱這樣:
Javapublic Object[] toArray(Object[] a)
Generics 出現(xiàn)后,許多類和方法就變成 generic 的了。這個(gè)方法也隨大流聲明成這樣:
Javapublic <T> T[] toArray(T[] a)
這樣聲明可以與 Java 1.5 之前的版本兼容10。
4 多啰嗦兩句
這個(gè)方法需要一個(gè)數(shù)組參數(shù)。如果這個(gè)數(shù)組的 length 大于或等于當(dāng)前 list 的 size,list 中的元素就會(huì)存儲(chǔ)到這個(gè)數(shù)組當(dāng)中;如果這個(gè)數(shù)組的 length 小于當(dāng)前 list 的 size,就會(huì)創(chuàng)建一個(gè)新的數(shù)組,并把當(dāng)前 list 中的元素存入到這個(gè)新創(chuàng)建的數(shù)組中。為提高效率,如果可能,傳入的數(shù)組的 length 要大于或等于 list 的 size,以避免該方法新建數(shù)組。
List<Integer> intList = new ArrayList<Integer>(); intList.add(); intList.add(); //傳入一個(gè)數(shù)組,它的長(zhǎng)度為 Number[] numArray = intList.toArray(new Number[]); //語(yǔ)句 //傳入一個(gè)數(shù)組,它的長(zhǎng)度與 intList 的長(zhǎng)度相等 Number[] numArray = intList.toArray(new Number[intList.size()]); //語(yǔ)句
另外,作為參數(shù)的數(shù)組不能為 null ,否則的話會(huì)拋出 NullPointerException 異常。
Footnotes:
1
Effective Java (2nd Edition)
2
Link
3
Link
4
Link
5
Link
6
Link
7
Link
8
Link
9
Link
10
Link
Created: 2016-04-06 Wed 21:14
Emacs 24.5.1 (Org mode 8.2.10)
Validate
以上內(nèi)容是小編給大家介紹的Java ArrayList.toArray(T[]) 方法的參數(shù)類型是 T 而不是 E的原因分析,希望對(duì)大家有所幫助!
相關(guān)文章
手把手教你使用IDEA創(chuàng)建多模塊(maven)項(xiàng)目
這篇文章主要給大家介紹了關(guān)于如何使用IDEA創(chuàng)建多模塊(maven)項(xiàng)目的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07Mybatis in條件傳參的三種實(shí)現(xiàn)方式(直接$,List,[])
這篇文章主要介紹了Mybatis in條件傳參的三種實(shí)現(xiàn)方式(直接$,List,[]),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12SpringBoot集成JPA持久層框架,簡(jiǎn)化數(shù)據(jù)庫(kù)操作
JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化規(guī)范。主要是為了簡(jiǎn)化持久層開發(fā)以及整合ORM技術(shù),結(jié)束Hibernate、TopLink、JDO等ORM框架各自為營(yíng)的局面。JPA是在吸收現(xiàn)有ORM框架的基礎(chǔ)上發(fā)展而來(lái),易于使用,伸縮性強(qiáng)。2021-06-06Intellij idea 代碼提示忽略字母大小寫和常用快捷鍵及設(shè)置步驟
這篇文章主要介紹了Intellij idea 代碼提示忽略字母大小寫和常用快捷鍵及設(shè)置步驟,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02