獲取和放入原則
原文連結: The Get and Put Principle
本文僅供學習和交流使用,如果您發現我已經侵犯到原作者的版權,請郵件我ttchgm@gmail.com。以便我及時刪除和處理。如果翻譯有錯誤或者交流可以隨時mail我。或者在sina微博 @天天吃好,私信與我。 本文拒絕任何形式轉載。
好的慣例才可能更好的嵌入萬用字元,但是如何決定在哪些情況下使用萬用字元呢?什麼地方使用extends,什麼地方使用super,什麼情況什麼地方不適合使用萬用字元?
幸運的是,一個簡單的原則規定決定了什麼情況下適合使用萬用字元、extends、super。
獲取和放入原則 : 僅當你從一個結構中獲取值的時候適合使用extends萬用字元,僅當你放入值到一個結構中的時候適合使用super萬用字元,當同時存在獲取或者放入操作的時候不能使用萬用字元。
我們已經看見這個原則存在copy函式的簽名中
public static void copy(List<? super T> dest, List<? extends T> src)
函式在源列表中獲取值,所以宣告瞭一個extends萬用字元,放入到目的列表dst中,所以宣告瞭一個super萬用字元。每當你使用一個迭代器,你從一個結構中獲取值,所以使用一個extemds萬用字元。下面的函式從一個數字的集合中獲取值,都每個值都轉換成雙精度值,並把他們相加後的結果返回:
public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums) s += num.doubleValue();
return s;
}
下面是使用extends之後的所有合法的呼叫形式:
List<Integer> ints = Arrays.asList(1,2,3);
assert sum(ints) == 6.0;
List<Double> doubles = Arrays.asList(2.78,3.14);
assert sum(doubles) == 5.92;
List<Number> nums = Arrays.<Number>asList(1,2,2.78,3.14);
assert sum(nums) == 8.92;
上面前兩個呼叫如果沒有使用extends是非法的。每當你使用add函式,放入值到一個結構中,因此需要使用super萬用字元。下面一個函式給出了一個數字集合和一個整型 n ,並且從 0 開始到整型 n 範圍內的值放入集合中。
public static void count(Collection<? super Integer> ints, int n) {
for (int i = 0; i < n; i++) ints.add(i);
}
下面是使用super之後的所有合法的呼叫形式:
List<Integer> ints = new ArrayList<Integer>();
count(ints, 5);
assert ints.toString().equals("[0, 1, 2, 3, 4]");
List<Number> nums = new ArrayList<Number>();
count(nums, 5); nums.add(5.0);
assert nums.toString().equals("[0, 1, 2, 3, 4, 5.0]");
List<Object> objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
assert objs.toString().equals("[0, 1, 2, 3, 4, five]");
上面最後兩個如果沒有使用super是非法的。每當你在相同結構裡放入值和獲取值的時候,你不應該使用萬用字元了。
public static double sumCount(Collection<Number> nums, int n) {
count(nums, n);
return sum(nums);
}
一個集合同時呼叫sum和count,因此他的元素型別必須同時繼承Number(例如sum的需要),並且是Integer的超類(例如count的需要)。僅有Number和Integer這兩個類滿足同時呼叫sum和count的需求,我們摘取其中以個類呼叫,例子如下:
List<Number> nums = new ArrayList<Number>();
double sum = sumCount(nums,5);
assert sum == 10;
因為沒有萬用字元,引數必須是一個數字型的集合。如果你不喜歡在Number和 Integer之間選擇的話。 如果java可以允許你寫一個extends和super同時存在的萬用字元,你就不需要選擇了。如下例項,我們可以這樣寫:
double sumCount(Collection<? extends Number super Integer> coll, int n)
//java中不合法!
那麼我們在另一個數字型的集合裡或者一個整形的集合裡呼叫sumCount。但是java並不允許這樣。不合法的唯一原因是天真,真希望java未來的可以支援這樣。但是,現在,如果你需要同時獲取和放入那麼不能使用萬用字元。
Get和Put原則也工作在其他的方式。如果一個extends萬用字元出現,你能做的是獲取,而不是放入;並且如果一個super萬用字元出現,你能做的是放入,而不是獲取。
思考如下例子程式碼,使用一個extends萬用字元宣告一個列表:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
double dbl = sum(nums); // ok
nums.add(3.14); // 編譯錯誤
sum呼叫沒問題,因為他從列表中獲取值,但add呼叫有問題,因為他把值放入一個列表。這樣不錯,不然的話我們就將一個雙精度值增加到了一個整型列表中!
相反的,思考如下程式碼段,使用一個extends萬用字元宣告一個列表:
List<Object> objs = new ArrayList<Object>();
objs.add(1);
objs.add("two");
List<? super Integer> ints = objs;
ints.add(3); // ok
double dbl = sum(ints); // compile-time error
現在add呼叫沒問題,因為他放入一個值到列表中,但sum呼叫有問題,因為他從一個列表中獲取值。這樣不錯,因為一個列表的和不能包含一個毫無意義的字串!
異常能證明規則,並且不同規則都擁有一個異常。一個宣告為extends萬用字元的型別不能放入任何值,除了null值,他屬於所有任何引用型別的子類:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(null); // ok
assert nums.toString().equals("[1, 2, null]");
同樣,你不能從一個宣告為super萬用字元的型別裡獲得任何值,除了這個的型別是Object,Object是任何引用型別的超類:
List<Object> objs = Arrays.<Object>asList(1,"two");
List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString();
assert str.equals("1two");
你是否可以發現,這有助於思考 ? extends T 包含null型別之上和T之下的界限內的任何型別(任何情況下型別null都是任何引用型別的子型別)。同樣的,你可以思考一下?super T包含T之下和Object之上的任何型別。
一個誘人的想法,extends萬用字元確保不可變,但是我們在早先的看到他並不是,指定一個列表型別為List<? Extends Number>,你仍然可以增加 null 值到列表中。你也可以移除列表中的元素(使用remove,removeAll或者retainAll)或者列表的排列(使用swap,sort,或者shuffle在便捷類Collections中,看17.1.1節)。
如果你確定想讓一個列表不會被改變,使用集合類中的函式unmodifiableList就可以做到這一點;類似的功能的奇特函式存在collection類中(譯者注:jdk1.6以上的版本存在於Collections便捷類中)(看17.3.2)節。如果確定想讓列表中的元素不能被改變,考慮使用規則來建立一個不可變的類,可以參看Joshua bloch的書 Effective Java(Addison-Wesley)中的第4章(item “Minimize mutability”/“Favor immutability”);
如下例子,在第二部分,在12.1節的CodingTask和PhoneTask類,在13.2節的類PriorityTask 是不可變的。
因為String 是final的,不能擁有子類,所以你可以認為List和List<? extends String >是同樣的型別。但事實上,前面的形式是後面的一個子類,並不是同一個型別,正如我們看見的應用程式的原則一樣。替換原則告訴我們他是一個子型別,因為他很容易通過一個值的型別形式,預測後面的型別。獲取和放入原則告訴我們,他不是同樣的型別,因為後面的型別定義不能add一個字串型別形式的值。
相關文章
- 如何建立和獲取正則物件?物件
- Struts2筆記10 向值棧放入或獲取資料筆記
- 短影片運營如何獲取流量?要遵循四大原則!
- PHP 正則獲取域名(一級域名)PHP
- div和span的使用原則
- OCP原則——開閉原則
- 搜狐架構大調整:成立原創和內容獲取中心架構
- datatables 獲取 pageLength 和 pageStart,重新獲取table資料
- GBase8a分佈列選取原則
- CodeReview 的經驗和原則View
- 設計原則:開閉原則(OCP)
- Qt:獲取日期和時間QT
- java獲取日期和時間Java
- 理解敏捷的價值觀和原則敏捷
- 物件導向設計原則和模式物件模式
- 資料治理的目標和原則
- Java中的設計模式和原則Java設計模式
- Angular 2.0 的設計方法和原則Angular
- PPT製作新方法和原則
- SMART原則的定義和含義
- 設計原則:介面隔離原則(ISP)
- SOLDI原則之DIP:依賴倒置原則
- 設計原則之【介面隔離原則】
- 設計原則-依賴反轉原則
- jquery 獲取select框選中的值示例一則jQuery
- oop原則OOP
- Oracle獲取所有表名資訊和獲取指定表名欄位資訊Oracle
- 設計原則之【依賴反轉原則】
- 設計原則之【單一職責原則】
- 設計原則之【開放封閉原則】
- 設計原則之【裡式替換原則】
- 軟體設計原則—介面隔離原則
- 軟體設計原則—合成複用原則
- 靜態初始化中不能放入繁重計算,否則會變慢!
- (原)BOM斷階原則
- 整理獲取 viewport 和 element 尺寸和位置方法View
- iOS WebView UserAgent 獲取和設定iOSWebView
- 獲取和設定pdf目錄