日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为什么我不信任通配符,以及为什么我们仍然需要通配符

發布時間:2023/12/3 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么我不信任通配符,以及为什么我们仍然需要通配符 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在將子類型多態性(面向對象)與參數多態性(泛型)相結合的任何編程語言中,都會出現方差問題。 假設我有一個字符串列表,鍵入List<String> 。 我可以將其傳遞給接受List<Object>的函數嗎? 讓我們從這個定義開始:



interface List<T> {void add(T element);Iterator<T> iterator();... }

破碎的協方差

憑直覺,我們可能首先認為應該允許這樣做。 看起來不錯:

void iterate(List<Object> list) {Iterator<Object> it = list.iterator();... } iterate(ArrayList<String>());

確實,包括Eiffel和Dart在內的某些語言確實接受此代碼。 可悲的是,它是不完善的,如以下示例所示:

//Eiffel/Dart-like language with //broken covariance: void put(List<Object> list) {list.add(10); } put(ArrayList<String>());

在這里,我們將List<String>傳遞給接受List<Object>的函數,該函數嘗試將Integer添加到列表中。

Java使用數組也會犯同樣的錯誤。 以下代碼編譯:

//Java: void put(Object[] list) {list[0]=10; } put(new String[1]);

它在運行時失敗,并帶有ArrayStoreException 。

使用地點差異

Java對于通用類和接口類型采用了不同的方法。 默認情況下,類或接口類型為invariant ,即:

  • 當且僅當U與V完全相同類型時,才可將L<V>分配給L<V> 。

由于在很多時候這非常不方便,因此Java支持一種稱為“ 使用站點差異”的方法 ,其中:

  • L<U>可分配給L<? extends V> 如果U是V的子類型,則L<? extends V> ,并且
  • L<U>可分配給L<? super V> L<? super V>如果U是的超類型V 。

丑陋的語法? extends V ? extends V或? super V ? super V稱為通配符 。 我們還說:

  • L<? extends V> L<? extends V>在V是協變的,并且
  • L<? super V> L<? super V>在V是反變的。

由于Java的通配符表示法很丑陋,因此在本討論中我們將不再使用它。 取而代之的是,我們將分別使用關鍵字in和out來表示通變量和協方差。 從而:

  • L<out V>在V是協變的,并且
  • L<in V>是在逆變 V 。

給定的V稱為通配符的邊界 :

  • out V是一個上限通配符, V是其上限,并且
  • in V是下界通配符, V是其下界。

從理論上講,我們可以有一個具有上限和下限的通配符,例如L<out X in Y> 。
我們可以使用交集類型表示多個上限或多個下限,例如L<out U&V>或L<in U&V> 。
請注意,類型表達式L<out Anything>和L<in Nothing>指的是完全相同的類型,并且此類型是L的所有實例的超類型。 您會經常看到人們將通配符類型稱為存在性類型 。 他們的意思是,如果我知道該list的類型為List<out Object> :

List<out Object> list;

然后我知道存在一個未知的類型T ,這是Object的子類型,因此list的類型為List<T> 。
或者,我們可以從更寬泛的角度出發,說List<out Object>是所有List<T>類型的并集,其中T是Object的子類型。
在具有使用地點差異的系統中,以下代碼無法編譯:

void iterate(List<Object> list) {Iterator<Object> it = list.iterator();... } iterate(ArrayList<String>()); //error: List<String> not a List<Object>

但是這段代碼可以做到:

void iterate(List<out Object> list) {Iterator<out Object> it = list.iterator();... } iterate(ArrayList<String>());

正確地,此代碼無法編譯:

void put(List<out Object> list) {list.add(10); //error: Integer is not a Nothing } put(ArrayList<String>());

現在我們在兔子洞的入口。 為了將通配符類型集成到類型系統中,同時拒絕如上例所示的錯誤代碼,我們需要一種更為復雜的類型參數替換算法。

會員輸入使用地點差異

也就是說,當我們有一個泛型類型類似List<T>有一種方法void add(T element) ,而不是僅僅直截了當代Object的T ,就像我們做普通不變的類型,我們需要考慮的方差類型參數出現的位置。 在這種情況下, T出現在List類型的反位置 ,即作為方法參數的類型。 我不會在這里寫下的復雜算法告訴我們,在此位置我們應該用Nothing (底部類型)代替。
現在想象一下我們的List接口有一個帶有以下簽名的partition()方法:

interface List<T> {List<List<T>> partition(Integer length);... }

List<out Y>的partition()的返回類型是什么? 好吧,在不損失精度的情況下,它是:

List<in List<in Y out Nothing> out List<in Nothing out Y>>

哎喲。
由于沒有人在他們的頭腦中想去考慮這樣的類型,因此明智的語言會拋棄其中的某些界限,留下這樣的東西:

List<out List<out Y>>

這是可以接受的。 不幸的是,即使在這種非常簡單的情況下,我們也已經遠遠超出了程序員可以輕松跟隨類型檢查器所做的工作的地步。
因此,這就是我不信任使用地點差異的原因所在:

  • Ceylon設計的一個重要原則是,程序員應始終能夠重現編譯器的推理。 這是原因的一些與使用現場方差出現的復雜類型的非常困難。
  • 它具有病毒性作用:一旦這些通配符類型在代碼中立足,它們便開始傳播,很難回到我的普通不變式類型。

申報地點差異

使用場所方差的一個更合理的選擇是聲明場所方差 ,在聲明時我們指定泛型類型的方差。 這是我們在錫蘭使用的系統。 在此系統下,我們需要將List分為三個接口:

interface List<out T> {Iterator<T> iterator();List<List<T>> partition(Integer length);... }interface ListMutator<in T> {void add(T element); }interface MutableList<T>satisfies List<T>&ListMutator<T> {}

List聲明為協變類型, ListMutator為逆變類型, MutableList為兩者的不變子類型。
似乎對多個接口的需求似乎是聲明站點差異的一個很大的缺點,但事實證明,將變異與讀取操作分開是很有用的,并且:

  • 變異運算通常是不變的,而
  • 讀取操作通常是協變的。

現在我們可以這樣編寫函數:

void iterate(List<Object> list) {Iterator<Object> it = list.iterator();... } iterate(ArrayList<String>());void put(ListMutator<Integer> list) {list.add(10); } put(ArrayList<String>()); //error: List<String> is not a ListMutator<Integer>

您可以在此處閱讀有關聲明位置差異的更多信息。

為什么我們在錫蘭需要使用場所差異

可悲的是,Java沒有聲明站點差異,并且與Java的干凈互操作對我們來說很重要。 我不喜歡純粹為了與Java互操作而在語言的類型系統中添加主要功能,因此多年來,我一直拒絕向Ceylon添加通配符。 最后,現實和實用性獲勝,我的頑固失去了。 因此,錫蘭1.1現在具有帶有單界通配符的使用站點差異。
我試圖盡可能嚴格地限制此功能,而僅提供體面的Java互操作所需的最低限度。 這意味著,就像在Java中一樣:

  • 沒有形式為List<in X out Y>雙界通配符,并且
  • 通配符類型不能出現在類或接口定義的extends或satisfies子句中。

此外,與Java不同:

  • 沒有隱含界的通配符,上限必須始終以顯式形式編寫,并且
  • 不支持通配符捕獲 。

通配符捕獲是Java的一個非常聰明的功能,它利用了通配符類型的“現有”解釋。 給定這樣的通用函數:

List<T> unmodifiableList<T>(List<T> list) => ... :

Java讓我調用unmodifiableList() ,傳遞一個通配符類型,如List<out Object> ,返回另一個通配符List<out Object> ,理由是存在一些未知的X ,這是Object的子類型,對其進行調用是正確的。 也就是說,即使無法為任何T將List<out Object>類型分配給List<T> ,此代碼也被認為是類型正確的代碼:

List<out Object> objects = .... ; List<out Object> unmodifiable = unmodifiableList(objects);

在Java中,涉及通配符捕獲的鍵入錯誤幾乎是無法理解的,因為它們涉及未知且難以理解的類型。 我沒有計劃向錫蘭添加對通配符捕獲的支持。

試試看

使用站點差異已經實現,并且已經在Ceylon 1.1中起作用,如果您非常有動力,可以從GitHub獲得。
即使此功能的主要動機是強大的Java互操作性,但在通配符很有用的其他場合(可能很少見)。 但是,這并不表示我們的方法有任何重大變化。 除極端情況外,我們將繼續在Ceylon SDK中使用聲明站點差異。 更新: 我只是意識到我忘了感謝Ross Tate,感謝他為我提供了有關使用站點差異的成員鍵入算法的詳細知識。 羅斯知道這些非常棘手的東西!

翻譯自: https://www.javacodegeeks.com/2014/08/why-i-distrust-wildcards-and-why-we-need-them-anyway.html

總結

以上是生活随笔為你收集整理的为什么我不信任通配符,以及为什么我们仍然需要通配符的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。