Effective Java筆記(31)利用有限制通配符來提升 API 的靈活性

????????參數化類型是不變的( invariant ) 。 換句話說,對于任何兩個截然不同的類型 Typel 和 Type2 而言, List<Type1?>既不是 List<Type 2 > 的子類型,也不是它的超類型 。雖然 L ist<String>不是 List<Object>的子類型,這與直覺相悖,但是實際上很有意義 。你可以將任何對象放進一個List<Object>中,卻只能將字符串放進 List<String>中 。由于 List<String>不能像 List<O句ect> 能做任何事情,它不是一個子類型 。

????????有時候,我們需要的靈活性要比不變類型所能提供的更多 。比如第 29 條中的堆樓 。 提醒一下,下面就是它的公共 API:

public class Stack<E> {public Stack();public void push(E e);public E pop();public boolean isEmpty();
}

????????假設我們想要增加一個方法,讓它按順序將一系列的元素全部放到堆枝中 。 第一次嘗試如下:

public void pushAll(Iterable<E> src) {for(E e : src)push(e);
}

????????這個方法編譯時正確無誤,但是并非盡如人意 。 如果 Iterable 的 src 元素類型與堆棧的完全匹配,就沒有問題 。 但是假如有一個 Stack<Number>,并且調用了 push (intVal),這里的工ntVal 就是 Integer 類型 。 這是可以的,因為 Integer 是 Number 的一個子類型 。 因此從邏輯上來說,下面這個方法應該可行 :

Stack <Number> numberStack = new Stack<>() ;
Iterable<Integer> integers = ...;
numberStack. pushAll(integers);

但是,如果嘗試這么做,就會得到下面的錯誤消息,因為參數化類型是不可變的:

????????幸運的是,有一種解決辦法 。Java 提供了一種特殊的參數化類型,稱作有限制的通配符類型(bounded wildcard type ),它可以處理類似的情況 。pushAll 的輸入參數類型不應該為“ E 的 Iterable 接口”,而應該為“ E 的某個子類型的 Iterable 接口”通配符類型Iterable<?extends E >正是這個意思 。 (使用關鍵字 ex ten也有些誤導 :回憶一下第29 條中的說法,確定了子類型( subtype )后,每個類型便都是自身的子類型,即使它沒有將自身擴展 。)我們修改一下 pushAll 來使用這個類型:

public void pushAll(Iterable<? extends E> src) {for(E e : src)push(e);
}

????????修改之后,不僅 Stack 可以正確無誤地編譯,沒有通過初始的 pushAll 聲明進行編譯的客戶端代碼也一樣可以 。 因為 Stack 及其客戶端正確無誤地進行了編譯,你就知道一切都是類型安全的了 。

????????現在假設想要編寫一個 pushAll 方法,使之與 popAll 方法相呼應 。popAll 方法從堆校中彈出每個元素,并將這些元素添加到指定的集合中 。 初次嘗試編寫的 popAll 方法可能像下面這樣 :

public void popAll(Col1ection<E> dst) {while (!isEmpty())dst.add(pop());
}

????????此外,如果目標集合的元素類型與堆棧的完全匹配,這段代碼編譯時還是會正確無誤,并且運行良好 。 但是,也并不意味著盡如人意 。 假設你有一個 Stack<Number >和 Object 類型的變量 。 如果從堆校中彈出 一個元素,并將它保存在該變量中,它的編譯和運行都不會出錯,那你為何不能也這么做呢?

Stack<Number> numberStack = new Stack<Number>() ;
Collection<Object> objects = ...;
numberStack.popAll(objects) ;

????????如果試著用上述 的 popAll 版本編譯這段客戶端代碼,就會得到一個非常類似于第一次用 pushAll 時所得到的錯誤:Collection<Object >不是 Collection<Number>的子類型 。 這一次通配符類型同樣提供了一種解決辦法 。popAll 的輸入參數類型不應該為“ E 的集合”,而應該為“ E 的某種超類的集合”(這里的超類是確定的,因此 E 是它自身的一個超類型)。 仍有一個通配符類型正符合此意:Collection<? super E > 。 讓我們修改 popAll 來使用它:

public void popAll(Collection<? super E> dst) {while (!isEmpty())dst.add(pop();
}

????????做了 這個變動之后,Stack 和客戶端代碼就都可以正確無誤地編譯了 。

????????結論很明顯:為了獲得最大限度的靈活性,要在表示生產者或者消費者的輸入參數上使用通配符類型 。 如果某個輸入參數既是生產者,又是消費者,那么通配符類型對你就沒有什么好處了:因為你需要的是嚴格的類型匹配,這是不用任何通配符而得到的 。

????????下面的助記符便于讓你記住要使用哪種通配符類型 :

????????PECS 表示 producer-extends,consumer-super

????????換句話說,如果參數化類型表示一個生產者 T ,就使用<? extends T >;如果它表示一個消 費者 T ,就使用 <? super T > 。 在我們的 Stack 示例中,pushAll 的 src 參數產生 E 實 例供 Stack 使用 ,因 此 src 相 應的類型為 Iterable<? extends E> ; popAll的 dst 參數通過 Stack 消費 E 實例,因此 dst 相應的類型為 Collection<? s uper E > 。PECS 這個助記符突 出了使用通配符類型的基本原則 。Naftalin 和 Wadler 稱之為 Get αnd Put Principle。

????????如果使用得當,通配符類型對于類的用戶來說幾乎是無形的 。 它們使方法能夠接受它們應該接受的參數,并拒絕那些應該拒絕的參數 。 如果類的 用 戶必須考慮通配符類型,類的API 或許就會出錯

????????一般來說, 如果類型參數只在方法聲明中出現一次,就可以用通配符取代它 。 如果是無限制的類型參數,就用無限制的通配符取代它;如果是有限制的類型參數,就用有限制的通配符取代它。

????????總而言之,在 API 中使用通配符類型雖然比較需要技巧,但是會使 API 變得靈活得多 。 如果編寫 的是將被廣泛使用的類庫, 則一定要適當地利用通配符類型 。 記住基本的原則:producer-extends,consumer-super(PECS ) 。 還要記住所有的 comparable 和comparator 都是消費者 。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/34896.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/34896.shtml
英文地址,請注明出處:http://en.pswp.cn/news/34896.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Oracle自定義函數生成MySQL表結構的DDL語句

1. 自定義函數fnc_table_to_mysql create or replace function fnc_table_to_mysql ( i_owner in string, i_table_name in string, i_number_default_type in string : decimal, i_auto_incretment_column_name in stri…

Linux 文件查看命令

一、cat命令 1.cat文件名&#xff0c;查看文件內容&#xff1a; 例如&#xff0c;查看main.c文件的內容&#xff1a; 2.cat < 文件名&#xff0c;往文件中寫入數據&#xff0c; Ctrld是結束輸入 例如&#xff0c;向文件a.txt中寫入數據&#xff1a; 查看剛剛寫入a.txt的…

Yolov5(一)VOC劃分數據集、VOC轉YOLO數據集

代碼使用方法注意修改一下路徑、驗證集比例、類別名稱&#xff0c;其他均不需要改動&#xff0c;自動劃分訓練集、驗證集、建好全部文件夾、一鍵自動生成Yolo格式數據集在當前目錄下&#xff0c;大家可以直接修改相應的配置文件進行訓練。 目錄 使用方法&#xff1a; 全部代碼…

解決監督學習,深度學習報錯:AttributeError: ‘xxx‘ object has no attribute ‘module‘!!!!

哈嘍小伙伴們大家好呀&#xff0c;很長時間沒有更新啦&#xff0c;最近在研究一個問題&#xff0c;就是AttributeError: xxx object has no attribute module 今天終于是解決了&#xff0c;所以來記錄分享一下&#xff1a; 我這里出現的問題是&#xff1a; 因為我的數據比較大…

SQL優化

一、插入數據 優化 1.1 普通插入&#xff08;小數據量&#xff09; 普通插入&#xff08;小數據量&#xff09;&#xff1a; 采用批量插入&#xff08;一次插入的數據不建議超過1000條&#xff09;手動提交事務主鍵順序插入 1.2 大批量數據插入 大批量插入&#xff1a;&…

Android 開發中需要了解的 Gradle 知識

作者&#xff1a;wkxjc Gradle 是一個基于 Groovy 的構建工具&#xff0c;用于構建 Android 應用程序。在 Android 開發中&#xff0c;了解 Gradle 是非常重要的&#xff0c;因為它是 Android Studio 默認的構建工具&#xff0c;可以幫助我們管理依賴項、構建應用程序、運行測試…

macOS 如何安裝git和nvm

首先&#xff1a;先來安裝git 打開macOS終端 將下面的命令復制粘貼進去&#xff1a; curl -O https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.41.0.tar.gz 版本號可以參考一下官網的 我這里安裝的是目前最新的2.41.0 然后在終端輸入下面的代碼或者雙擊git的…

數據結構:力扣OJ題

目錄 ?編輯題一&#xff1a;鏈表分割 思路一&#xff1a; 題二&#xff1a;相交鏈表 思路一&#xff1a; 題三&#xff1a;環形鏈表 思路一&#xff1a; 題四&#xff1a;鏈表的回文結構 思路一&#xff1a; 鏈表反轉&#xff1a; 查找中間節點&#xff1a; 本人實力…

YOLOv8+ByteTrack多目標跟蹤(行人車輛計數與越界識別)

課程鏈接&#xff1a;https://edu.csdn.net/course/detail/38901 ByteTrack是發表于2022年的ECCV國際會議的先進的多目標跟蹤算法。YOLOv8代碼中已集成了ByteTrack。本課程使用YOLOv8和ByteTrack對視頻中的行人、車輛做多目標跟蹤計數與越界識別&#xff0c;開展YOLOv8目標檢測…

Leetcode每日一題:23. 合并 K 個升序鏈表(2023.8.12 C++)

目錄 23. 合并 K 個升序鏈表 題目描述&#xff1a; 實現代碼與解析&#xff1a; 優先級隊列&#xff1a; 原理思路&#xff1a; 23. 合并 K 個升序鏈表 題目描述&#xff1a; 給你一個鏈表數組&#xff0c;每個鏈表都已經按升序排列。 請你將所有鏈表合并到一個升序鏈表…

Flutter: A RenderFlex overflowed by 42 pixels on the bottom.

Flutter&#xff1a;渲染活動底部上方溢出了42個像素 Flutter 控件超出異常&#xff1a;A RenderFlex overflowed by 42 pixels on the bottom. 解決方案 1.Scaffold內添加 resizeToAvoidBottomInset 屬性&#xff0c;缺點是軟鍵盤下面的控件被擋住 Scaffold( resizeToAvoidBot…

第一百二十七天學習記錄:我的創作紀念日

機緣 今天收到CSDN官方的來信&#xff0c;想想也可以對我前面的學習記錄進行一個總結。 關于來到CSDN的初心&#xff0c;也就是為了讓自己養成一個良好的學習總結的習慣。這里要感謝我C語言視頻教程的老師&#xff0c;是他建議學生們在技術博客中進行記錄。對于技術博客&…

web-Element

在vueapp里<div><!-- <h1>{{message}}</h1> --><element-view></element-view></div> <div><!-- <h1>{{message}}</h1> --><element-view></element-view></div>在view新建個文件 <t…

C++ VTK 8.2 如何繪制彈簧圖形

//創建圓柱 vtkSmartPointer<vtkCylinderSource> spCylinderSource vtkSmartPointer<vtkCylinderSource>::New(); spCylinderSource->SetHeight(m_dCylinderHeight); // 設置圓柱的高度 spCylinderSource->SetRadius(m_dCylinderRadius)…

Spring(12) BeanFactory 和 ApplicationContext 區別

目錄 一、BeanFactory 和 ApplicationContext 區別&#xff1f;二、既然 Spring Boot 中使用的是 ApplicationContext 進行應用程序的啟動和管理&#xff0c;那么 Spring Boot 會用到 BeanFactory 嗎&#xff1f; 一、BeanFactory 和 ApplicationContext 區別&#xff1f; Bea…

git clone使用https協議報錯OpenSSL SSL_read: Connection was reset, errno 10054

在使用git 下載github上的代碼時&#xff0c; 一般有ssh協議和https協議兩種。使用ssh協議可以成功clone代碼&#xff0c; 但使用https協議時出錯&#xff1a; $ git clone https://github.com/openai/improved-diffusion.git Cloning into improved-diffusion... fatal: unab…

vue或uniapp使用pdf.js預覽

一、先下載穩定版的pdf.js&#xff0c;可以去官網下載 官網下載地址 或 pdf.js包下載(已配置好&#xff0c;無需修改) 二、下載好的pdf.js文件放在public下靜態文件里&#xff0c; uniapp是放在 static下靜態文件里 三、使用方式 1. vue項目 注意路徑 :src"static/pd…

每日一題 206反轉鏈表

題目 給你單鏈表的頭節點 head &#xff0c;請你反轉鏈表&#xff0c;并返回反轉后的鏈表。 示例 1&#xff1a; 輸入&#xff1a;head [1,2,3,4,5] 輸出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 輸入&#xff1a;head [1,2] 輸出&#xff1a;[2,1]示例 3&#xff1a; …

塊、行內塊水平垂直居中

1.定位實現水平垂直居中 <div class"outer"><div class"test inner1">定位實現水平垂直居中</div></div><style>.outer {width: 300px;height: 300px;border: 1px solid gray;margin: 100px auto 0;position: relative;}.te…

途樂證券-新股行情持續火爆,哪些因素影響首日表現?

全面注冊制以來&#xff0c;參加打新的投資者數量全體呈現下降。打新收益下降&#xff0c;破發頻出的布景下&#xff0c;投資者打新策略從逢新必打逐步向優選個股改變。 經過很多歷史數據&#xff0c;從商場定價、參加者熱度以及機構重視度維度揭秘了上市后股價體現優秀的個股具…