簡體   English   中英

為什么不從下一個 JVM 中刪除類型擦除?

[英]Why not remove type erasure from the next JVM?

Java 在 Java 5 中引入了帶有泛型的類型擦除,因此它們可以在舊版本的 Java 上工作。 這是兼容性的權衡。 我們已經失去了兼容性[1] [2] [3]——字節碼可以在更高版本的 JVM 上運行,但不能在早期版本上運行。 這看起來是更糟糕的選擇:我們已經丟失了類型信息,我們仍然無法在舊版本上運行為新版本 JVM 編譯的字節碼。 發生了什么?

具體來說,我是問是否有任何技術原因導致無法在下一版本的 JVM 中刪除類型擦除(假設,像以前的版本一樣,它的字節碼無論如何都無法在上一版本上運行)。

[3]:對於那些真正喜歡它的人來說,類型擦除可以以類似於retrolambda的方式向后移植。

編輯:我認為對向后與向前兼容性定義的討論掩蓋了這個問題。

類型擦除不僅僅是您可以打開或關閉的字節碼功能。

它影響整個運行時環境的工作方式。 如果您希望能夠查詢泛型類的每個實例的泛型類型,這意味着為泛型類的每個對象實例化創建與運行時Class表示相當的元信息。

如果你寫new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>() new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>() new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>()您不僅創建了三個對象,還可能創建了三個反映類型的額外元對象, ArrayList<String>ArrayList<Number>ArrayList<Object> ,如果它們之前不存在的話.

考慮到在典型應用程序中使用了數千種不同的List簽名,其中大多數從未在需要此類反射的地方使用過(由於缺少此功能,我們可以得出結論,目前,所有這些簽名沒有這樣的反射工作)。

當然,這乘以千種不同的泛型列表類型意味着千種不同的泛型迭代器類型、千種拆分器和流化身,甚至不包括實現的內部類。

它甚至會影響沒有對象分配的地方,這些地方當前正在探索引擎蓋下的類型擦除,例如Collections.emptyList()Function.identity()Comparator.naturalOrder()等。每次調用它們時都返回相同的實例. 如果您堅持讓特定捕獲的泛型類型可以反射檢查,這將不再起作用。 所以如果你寫

List<String> list=Collections.emptyList();
List<Number> list=Collections.emptyList();

您將不得不接收兩個不同的實例,每個實例在getClass()或未來的等效項上報告不同。


看來,希望獲得這種能力的人對他們的特定方法有一種狹隘的看法,如果他們能夠反思地發現一個特定參數實際上是二種或三種類型中的一種,那就太好了,但從不考慮攜帶的重量有關數千個泛型類的潛在成百上千個泛型實例的元信息。

這就是我們必須詢問我們得到什么回報的地方:支持有問題的編碼風格的能力(這是由於通過反射找到的信息而改變代碼行為的全部內容)。


到目前為止,答案僅解決了刪除類型擦除的簡單方面,以及內省實際實例類型的願望。 實際實例具有可以報告的具體類型。 正如提到的此評論用戶the8472 ,用於去除類型擦除的需求往往也意味着希望用於能夠鑄造到(T)或創建經由陣列new T[]或接入經由一個類型變量的類型T.class

這將引發真正的噩夢。 類型變量是與具體實例的實際類型不同的野獸。 類型變量可以解析為a,例如? extends Comparator<? super Number> ? extends Comparator<? super Number> ? extends Comparator<? super Number>舉一個(相當簡單的)例子。 提供必要的元信息意味着不僅對象分配變得更加昂貴,每個方法調用都可能增加這些額外的成本,甚至更大的擴展,因為我們現在不僅在談論泛型類與實際類的組合,而且還有所有可能的通配符組合,即使是嵌套的泛型類型。

請記住,類型參數的實際類型也可以引用其他類型參數,從而將類型檢查變成一個非常復雜的過程,如果允許從它,每個存儲操作都必須重復它。

除了沉重的性能問題外,復雜性還引發了另一個問題。 如果你查看javac的 bug 跟蹤列表或 Stackoverflow 的相關問題,你可能會注意到過程不僅復雜,而且容易出錯。 目前, javac每個次要版本都包含有關泛型類型簽名匹配的更改和修復,影響將被接受或拒絕的內容。 我很確定,您不希望像類型轉換、變量賦值或數組存儲這樣的內在 JVM 操作成為這種復雜性的受害者,對每個版本中什么是合法的或不合法的有不同的想法,或者突然拒絕javac接受的內容由於規則不匹配而導致編譯時。

在某種程度上, valhalla 項目將來會刪除擦除,以啟用值類型的專門實現。

或者更准確地說,類型擦除實際上意味着沒有泛型的類型特化,而 valhalla 將引入對原語的特化。

具體來說,我問是否有任何技術原因導致無法在下一版本的 JVM 中刪除類型擦除

性能。 您不必為泛型類型、實例或生成的類的所有組合生成專門的代碼不必攜帶類型標記、多態內聯緩存和運行時類型檢查(編譯器生成的instanceof檢查)保持簡單,我們仍然可以得到最多通過編譯時檢查的類型安全。

當然也有很多缺點,但已經做出了權衡,問題是什么會激勵 JVM 開發人員改變這種權衡。

這也可能是一個兼容性問題,可能有代碼執行未經檢查的強制轉換以通過依賴類型擦除來濫用泛型集合,如果強制執行類型約束就會中斷。

您對向后兼容性的理解是錯誤的。

期望的目標是JVM 能夠正確運行庫代碼,即使使用代碼也不會改變。 這允許用戶將他們的 Java 版本可靠地升級到比編寫代碼時更新的版本。

具體來說,我是問是否有任何技術原因導致無法在下一版本的 JVM 中刪除類型擦除(假設,像以前的版本一樣,它的字節碼無論如何都無法在上一版本上運行)。

因為它可能會破壞向后兼容性。 (真正的向后兼容性......不是你最初的問題所談論的那種神話般的。)

是的,這是一個技術原因,因為向后兼容性是 Java 語言/平台最重要的特性之一。 至少,這是甲骨文和甲骨文付費客戶的觀點。

如果類型擦除停止工作,那么在過去 20 年中,許多企業在基於 Java 的軟件上的數千億美元投資突然面臨風險。


對於那些真正喜歡它的人來說,類型擦除可以以類似於 retrolambda 的方式向后移植。

也許。 但需要有人最終證明這是可行的。 即使它被證明是可行的,改造、重新編寫代碼、重新測試等的成本也將是巨大的。


問問自己這個。 為什么 Sun/Oracle 沒有刪除Thread.stop()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM