我的理解是,在C#中爲泛型指定方差發生在類型聲明級別:當您創建泛型類型時,指定類型參數的方差。另一方面,在Java中,在使用泛型的地方指定了差異:當您創建某個泛型類型的變量時,可以指定其類型參數如何變化。Java的使用站點差異與C#的聲明站點差異相比如何?
每個選項有什麼優點和缺點?
我的理解是,在C#中爲泛型指定方差發生在類型聲明級別:當您創建泛型類型時,指定類型參數的方差。另一方面,在Java中,在使用泛型的地方指定了差異:當您創建某個泛型類型的變量時,可以指定其類型參數如何變化。Java的使用站點差異與C#的聲明站點差異相比如何?
每個選項有什麼優點和缺點?
我只是要回答聲明站點和使用站點差異之間的差異,因爲儘管C#和Java泛型在許多其他方面有所不同,但這些差異大部分與方差是正交的。首先,如果我沒有記錯,使用站點差異嚴格地比聲明站點差異更強大(儘管以簡潔爲代價),或者至少是Java的通配符(它實際上比使用站點差異更強大)。這種增強的能力對於那些使用大量有狀態結構的語言特別有用,比如C#和Java(但是Scala遠不如此,特別是因爲它的標準列表是不可變的)。考慮List<E>
(或IList<E>
)。既然它具有添加E和獲得E的方法,它對於E而言是不變的,因此不能使用聲明站點方差。但是,對於使用地點差異,您可以說List<+Number>
以獲得List
和List<-Number>
的協變子集以獲得List
的逆變子集。在聲明站點語言中,庫的設計者必須爲每個子集創建單獨的接口(或類,如果允許多個類的繼承)並且擴展那些接口。如果庫設計人員沒有這樣做(請注意,C#的IEnumerable
只做IList
的協變部分的一小部分),那麼你就運氣不好,而且你必須訴諸同樣的麻煩,你必須用一種語言任何種類的變化。
這就是聲明站點繼承的使用站點繼承的優點。聲明站點繼承與使用站點繼承的優點基本上是用戶的簡潔(只要設計人員將每個類/接口分離爲其協變和逆變部分)。對於諸如IEnumerable
或Iterator
之類的東西,最好不要在每次使用接口時指定協方差。 Java通過使用冗長的語法(除了Java的解決方案基本上是理想的雙變量)使得這特別惱人。
當然,這兩種語言功能可以共存。對於自然協變或逆變的類型參數(例如IEnumerable
/Iterator
),請在聲明中聲明。對於自然不變的類型參數(例如在(I)List
中),請聲明每次使用它時需要哪種方差。只是不要爲具有聲明站點差異的參數指定使用站點差異,因爲這會使事情混淆。
還有其他更詳細的問題我還沒有涉及(比如通配符實際上比使用網站方差更強大),但我希望這可以回答您對您的內容的問題。我承認我偏向於使用網站的差異,但我試圖描述在與程序員和語言研究人員討論時出現的兩個主要優點。
明智的答案,謝謝!使用使用站點差異自動將類型拆分爲共同部分和反對部分不是我想到的。 – munificent 2011-03-29 23:10:13
@monificent很高興你喜歡它。實際上,今年PLDI上有一篇論文發現,如果Java有聲明網站差異,則39%的使用網站差異(通配符)可能會變得多餘,但這也意味着61%的使用網站表達使用不可言說的聲明現場方差,而不會將類和接口分解爲co/contra/bi-variant組件。以爲你可能會發現有趣的統計數據;我當然做到了。 – 2011-04-07 21:44:39
哇,這是令人驚訝的。我希望數字更小,但是因爲Java沒有遺漏分離出可變性(即使'Iterator
大多數人似乎更喜歡聲明站點變化,因爲它更容易爲用戶庫的(同時使它有點困難的庫開發,但我會認爲,庫開發者思考無論實際寫入差異的方差如何)
但請記住,Java和C#都不是良好的語言設計示例。
雖然的Java了方差權,因爲在Java 5的兼容虛擬機的改進和類型擦除JVM的獨立工作,使用現場方差使得使用有點麻煩和具體的實現類型擦除已經繪製當之無愧的批評。
C#聲明網站差異的模型將負擔從圖書館用戶的負擔,但在他們引入泛化泛型期間,他們基本上建立了方差規則到他們的虛擬機。 即使在今天,他們也不能完全支持co// contravariance,因爲這個錯誤(並且非向下兼容的引入集合類已經將程序員分成兩個陣營)。
這對所有面向CLR的語言都提出了一個困難的限制,這也是爲什麼替代編程語言在JVM上生動得多的原因之一,儘管看起來CLR具有「更好的特性」。
讓我們看看Scala:Scala是在JVM上運行的完全面向對象的功能混合。 他們使用類型擦除像Java一樣,但泛型和(聲明站點)方差的實現比Java(或C#)更容易理解,更直接和更強大,因爲虛擬機不會對方差如何施加規則上班。 Scala編譯器檢查方差符號,並可以在編譯時拒絕不合理的源代碼,而不是在運行時拋出異常,而生成的.class文件可以無縫地從Java中使用。
聲明網站方差的一個缺點是它似乎使某些情況下的類型推斷更難。
與此同時,Scala可以使用原始類型,而不會像在C#中那樣通過使用@specialized
批註將其裝入集合中,從而告知Scala編譯器生成專用於所請求基元類型的一個或多個附加實現。
Scala還可以通過使用Manifests來「泛化」泛型,這使得它們可以在運行時像C#中一樣檢索泛型類型。 Java的泛型風格的
這對所有面向CLR的語言提出了一個困難的限制,並且是替代編程語言的一個原因雖然看起來CLR具有「更好的特性」,但在JVM上生動得多。< - 對我來說聽起來是無稽之談。在.net上實現Java風格泛型不應該很難。只要在.net的意義上將它們設爲非泛型,並以編譯器能夠理解的方式標記它們即可。 – CodesInChaos 2010-11-27 16:36:30
並且說在JVM上替代語言更加生動也許是錯誤的。兩種平臺上都有很多其他語言。 http://en.wikipedia.org/wiki/List_of_CLI_languages http://en.wikipedia.org/wiki/List_of_JVM_languages – CodesInChaos 2010-11-27 16:38:19
然後你放棄了所有與使用泛型的字節碼的互操作性。看看最近在C#上使用了多少泛型,除非你打算使用一種沒有互操作性的語言,否則這是不行的。有更多的語言,JVM有更多的語言開發。看看微軟最近如何放棄對IronRuby和IronPython的支持。 – soc 2010-11-27 16:41:02
缺點
一個結果是,Java版本只引用類型(或裝箱值類型),而不是值類型的作品。國際海事組織這是最大的缺點,因爲它在很多情況下阻止高性能泛型,並需要手動編寫特定類型。
它不保證像「這個列表只包含x類型的對象」這樣的不變量,並且需要在每個getter上進行運行時檢查。泛型類型確實存在。
使用反射時,您不能問一個通用對象的實例,它具有哪個通用參數。 Java的泛型風格的
優勢
你得到方差/可以在不同的泛型參數之間進行轉換。
C#也有類型參數的差異。我的問題更多地是關於使用站點差異(Java)與聲明站點(C#)的比較,而不是整體比較這些平臺上的泛型。 – munificent 2010-11-30 22:18:12
Java:自Java 5以來的使用站點差異泛型。自1.0以來使用不同的語法破壞了協變數組。泛型沒有運行時類型信息。
C#:自C#2.0以來的使用地點方差泛型。在C#4.0中添加聲明網站差異。自1.0以來破壞的協變數組使用不同的語法(與Java相同的問題)。 「通用」泛型意味着類型信息在編譯時不會丟失。
斯卡拉:自早期版本的語言(至少自2008年以來)都使用網站/聲明網站差異。數組不是一個單獨的語言功能,因此您使用相同的泛型語法和類型差異規則。某些集合在VM級別使用JVM數組實現,因此與Java代碼相比,您可以獲得相同或更好的運行時性能。
詳細闡述C#/ Java數組類型安全問題:您可以將Dog []轉換爲Pet []並添加一個Cat並觸發編譯時未捕獲到的運行時錯誤。斯卡拉正確實施了這一點。
一篇涉及這個問題的文章:http://cgi.di.uoa.gr/~smaragd/varj-ecoop12.pdf – Aivar 2012-12-25 18:02:38