為什麼 Java ArrayList.toArray(T[]) 方法的參數類型是 T 而不是 E ?

来源:http://www.cnblogs.com/xiaomiganfan/archive/2016/04/07/5362732.html
-Advertisement-
Play Games

前兩天給同事做 code review,感覺自己對 Java 的 Generics 掌握得不夠好,便拿出 《Effective Java》1 這本書再看看相關的章節。在 Item 24:Eliminate unchecked warnings 這一節中,作者拿 ArrayList 類中的 publi ...


前兩天給同事做 code review,感覺自己對 Java 的 Generics 掌握得不夠好,便拿出 《Effective Java》1 這本書再看看相關的章節。在 Item 24:Eliminate unchecked warnings 這一節中,作者拿 ArrayList 類中的 public <T> T[] toArray(T[] a) 方法作為例子來說明如何對變數使用 @SuppressWarnings annotation。

ArrayList 是一個 generic class,它是這樣聲明的:

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

這個類的 toArray(T[] a) 方法是一個 generic method,它是這樣聲明和實現的:

 1: @SuppressWarnings("unchecked")
 2: public <T> T[] toArray(T[] a) {
 3:     if (a.length < size)
 4:         // Make a new array of a's runtime type, but my contents:
 5:         return (T[]) Arrays.copyOf(elementData, size, a.getClass());
 6:     System.arraycopy(elementData, 0, a, 0, size);
 7:     if (a.length > size)
 8:         a[size] = null;
 9:     return a;
10: }

這個方法實際上是在 Collection 介面中聲明的。因為我們經常通過 ArrayList 使用它,這裡就用 ArrayList 作為例子了。

1 為什麼聲明為不同類型?

我的問題是:為什麼這個方法使用類型 T,而不使用 ArrayList 的類型 E ? 也就是說,這個方法為什麼不聲明成這樣:

public E[] toArray(E[] a);

如果類型相同的話,在編譯期間就可以發現參數的類型錯誤。如果類型不同,很容易產生運行時錯誤。比如下麵這段代碼:

1: //創建一個類型為 String 的 ArrayList
2: List<String> strList = new ArrayList<String>();
3: strList.add("abc");
4: strList.add("xyz");
5: //將當前的 strList 轉換成一個 Number 數組。註意,下麵的語句沒有任何編譯錯誤。
6: Number[] numArray = strList.toArray(new Number[0]);

運行上面的代碼, Line 6 會拋出 java.lang.ArrayStoreException 異常。

如果 toArray 方法使用類型 E 的話,語句2就會產生編譯錯誤。編譯錯誤怎麼說也比運行時錯誤親切啊。並且,generics 的主要目的就是為了類型安全,把類型轉換錯誤(ClassCastException)消滅在編譯期間。這個方法卻反其道而行之。難道這是一個大 bug? Java 的 bug 俺碰上過,但這個地方出 bug 我還是不太敢相信。

上網一查,這個問題早已被討論過多次了2, 3, 4

2 可以提高靈活性

這樣的聲明更靈活,可以把當前 list 中的元素轉換成一個更一般類型的數組。比如,當前 list 的類型是 Integer,我們可以把它的元素轉換成一個 Number 數組。

1: List<Integer> intList = new ArrayList<Integer>();
2: intList.add(1);
3: intList.add(2);
4: Number[] numArray = intList.toArray(new Number[0]);

如果這個方法聲明成類型 E,上面的代碼就會有編譯錯誤。 看起來,該方法聲明成下麵這樣會更合適:

public <T super E> T[] toArray(T[] a);

不過, <T super E> 這樣的語法在 Java 中是不存在的。而且即使存在,對數組也不起作用。也正是因為這個原因,在使用這個方法時,即使 T 是 E 的父類,或 T 跟 E 相同,也不能完全避免 java.lang.ArrayStoreException 異常5, 6, 7 。請看下麵兩段代碼。第一段代碼中 T 是 E 的父類,第二段代碼中 T 和 E 一樣。這兩段代碼都會拋出異常。

代碼一:

1: List<Integer> intList = new ArrayList<Integer>();
2: intList.add(1);
3: intList.add(2);
4: 
5: Float[] floatArray = new Float[2];
6: //Float 是 Number 的子類,所以 Float[] 是 Number[] 的子類
7: Number[] numArray = floatArray;
8: //下麵的語句會拋出 ArrayStoreException 異常
9: numArray = intList.toArray(numArray);

代碼二:

 1: List<Number> intList = new ArrayList<Number>();
 2: //List 的類型是 Number。但 Number 是抽象類,只能存它的子類的實例
 3: intList.add(new Integer(1));
 4: intList.add(new Integer(2));
 5: 
 6: Float[] floatArray = new Float[2];
 7: //Float 是 Number 的子類,所以 Float[] 是 Number[] 的子類
 8: Number[] numArray = floatArray;
 9: //下麵的語句會拋出 ArrayStoreException 異常
10: numArray = intList.toArray(numArray);

上面的異常都是由這個事實造成的:如果 A 是 B 的父類,那麼 A[] 是 B[] 的父類。Java 中所有的類都繼承自 Object,Object[] 是所有數組的父類。

這個帖子8里舉了個例子,說明即使這個方法的類型聲明成 E 也不能避免 ArrayStoreException 異常。

該方法的文檔中也提到了這個異常:

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 之前的版本相容

這個方法在 Java 引入 Generics 之前(JDK1.5 中引入了 Generics)就出現了9。那時它被聲明稱這樣:

public Object[] toArray(Object[] a)

Generics 出現後,許多類和方法就變成 generic 的了。這個方法也隨大流聲明成這樣:

public <T> T[] toArray(T[] a)

這樣聲明可以與 Java 1.5 之前的版本相容10

4 多啰嗦兩句

這個方法需要一個數組參數。如果這個數組的 length 大於或等於當前 list 的 size,list 中的元素就會存儲到這個數組當中;如果這個數組的 length 小於當前 list 的 size,就會創建一個新的數組,並把當前 list 中的元素存入到這個新創建的數組中。為提高效率,如果可能,傳入的數組的 length 要大於或等於 list 的 size,以避免該方法新建數組。

1: List<Integer> intList = new ArrayList<Integer>();
2: intList.add(1);
3: intList.add(2);
4: //傳入一個數組,它的長度為 0 
5: Number[] numArray1 = intList.toArray(new Number[0]); //語句1
6: //傳入一個數組,它的長度與 intList 的長度相等
7: Number[] numArray2 = intList.toArray(new Number[intList.size()]); //語句2

另外,作為參數的數組不能為 null ,否則的話會拋出 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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 創建二種方式 ...
  • 前言 建議13、為類型輸出格式化字元串 建議14、正確實現淺拷貝和深拷貝 建議15、使用dynamic來簡化反射實現 建議13、為類型輸出格式化字元串 有兩種方法可以為類型提供格式化的字元串輸出。 一種是意識到類型會產生格式化字元串輸出,於是讓類型繼承介面IFormattable。這對類型來說,是一 ...
  • JQuery UI 是以 JQuery 為基礎的開源 JavaScript 網頁用戶界面代碼庫。包含底層用戶交互、動畫、特效和可更換主題的可視控制項,這些控制項主要包括:Accordion,Autocomplete,ColorPicker,Dialog,Slider,Tabs,DatePicker,Ma ...
  • 查找了msdn上關於bool的介紹,整理如下: bool 關鍵字是 System.Boolean 的別名。它用於聲明變數來存儲布爾值 true 和 false。 如果需要一個也可以有 null 值的布爾型變數,請使用 bool?。 bool 變數的預設值為 false。bool? 變數的預設值為 n ...
  • ...
  • ...
  • 在採用了依賴註入的應用中,我們總是直接利用DI容器直接獲取所需的服務實例,換句話說,DI容器起到了一個服務提供者的角色,它能夠根據我們提供的服務描述信息提供一個可用的服務對象。ASP.NET Core中的DI容器體現為一個實現了IServiceProvider介面的對象。 一、ServiceProv... ...
  • HP-Socket 是一套通用的高性能 TCP/UDP 通信框架,包含服務端組件、客戶端組件和 Agent 組件,廣泛適用於各種不同應用場景的 TCP/UDP 通信系統,提供 C/C++、C#、Delphi、E(易語言)、Java、Python 等編程語言介面。HP-Socket 對通信層實現完全封 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...