該Collections.fill方法具有以下標題:
public static <T> void fill(List<? super T> list, T obj)
為什么需要通配符?以下標頭似乎也可以正常作業:
public static <T> void fill(List<T> list, T obj)
我看不出需要通配符的原因;如下代碼適用于第二個標頭以及第一個標頭:
List<Number> nums = new ArrayList<>();
Integer i = 43;
fill(nums, i); //fill method written using second header
我的問題是:對于fill第一個標題的具體呼叫,而不是第二個標題?如果沒有這樣的呼叫,為什么要包含通配符?在這種情況下,通配符不會使方法更簡潔,也不會增加可讀性(在我看來)。
uj5u.com熱心網友回復:
這是一個非常好的問題,并且已經猜到了簡單的答案:
對于當前版本
fill(List<? super T> list, T obj),如果簽名更改為fill(List<T> list, T obj),則不會拒絕此類輸入,因此沒有任何好處,并且開發人員可能遵循 PECS 原則
上面的陳述源于這樣的原則:如果存在這樣的型別,X那么它
X是的超型別,T那么List<X>是超型別,List<? super T>因為型別逆變。因為我們總能找到這樣的X(在最壞的情況下是Object類) - 編譯器可以在List<X>給定任何一種形式的fill.
因此,知道這一事實我們可以干擾編譯器并使用“型別見證”自己推斷型別,因此代碼會中斷:
List<Object> target = new ArrayList<>();
//Compiles OK as we can represent List<Object> as List<? super Integer> and it fits
Collections.<Integer>fill(target, 1);
//Compilation error as List<Object> is invariant to List<Integer> and not a valid substitute
Collections.<Integer>fillNew(target, 1);
這當然純粹是理論上的,沒有人會在他們的頭腦中使用型別引數。
然而
在回答“在這里使用通配符有什么好處? ”這個問題時,我們只考慮了等式的一方面——我們,方法的消費者和我們的經驗,而不是庫開發人員。
因此,這個問題有點類似于為什么Collections.enumeration(final Collection<T> c)宣告它,而不是這樣enumeration(Collection<T> c)的final,似乎是多余的最終用戶。
我們可以在這里推測真正的意圖,但我可以給出一些主觀原因:
- 第一:使用
List<? super T>(以及finalforenumeration)立即消除了代碼的歧義,并且更<? super T>具體地消除了代碼的歧義-它表明僅需要有關型別引數的部分知識并且list不能用于生成 T 的值,但僅用于消耗它們。參考:
通配符在只需要部分了解型別引數的情況下很有用。 JLS 4.5.1。引數化型別的型別引數
- 其次:它為庫所有者提供了一些自由來改進/更新方法,而不會破壞向后兼容性,同時符合現有的約束。
現在,讓我們嘗試做了一些假設性的“改進”,以明白我的意思(我會打電話的形式fill使用List<T>的fillNew):
#1 決定是使方法回傳obj值(用于填充串列):
public static <T> void fill(List<? super T> list, T obj)
//becomes ↓↓↓
public static <T> T fill(List<? super T> list, T obj)
更新后的方法可以很好地用于fill簽名,但是對于fillNew- 現在推斷的回傳型別并不那么明顯:
List<Number> target = new ArrayList<>();
Long val = fill(target, 1L); //<<Here Long is the most specific type that fits both arguments
//Compilation error
Long val = fillNew(target, 1L); //<<Here Number is, so it cannot be assigned back
//More exotic case:
Integer val = fill(asList(true), 0); //val is Integer as expected
Comparable<?> val = fillNew(asList(true), 0); //val is now Comparable<?> as the most specific type
#2添加的多載版本的決定fill是10倍的情況下具有更好的性能,當T是Comparable<T>:
/* Extremely performant 10x version */
public static <T extends Comparable<T>> void fill(List<? super T> list, T value)
/* Normal version */
public static void fill(List<? super T> list, T value)
List<Number> target = new ArrayList<>();
fill(target, 1); //<<< Here the more performant version is used as T inferred to Integer and it implements Comparable<Integer>
fillNew(target, 1); //<< Still uses the slow version just because T is inferred to Number which is not Comparable
總而言之 -fill在我看來,對于所有各方(開發人員和圖書館設計人員)來說,當前的簽名更靈活/更具描述性
uj5u.com熱心網友回復:
對于您的示例,它與您的基本<T>簽名“作業”的原因是整數也是一個數字。唯一有效的“T”是T = Number,然后整個事情就解決了。
在這種情況下,您對T obj引數的運算式是一個具體化型別:您有一個Integer. 你可以換一個T。也許你有這個:
class AtomicReference<T> {
// The actual impl of j.u.concurrent.AtomicReference...
// but with this one additional method:
public void fillIntoList(List<? super T> list) {
T currentValue = get();
Collections.fill(list, currentValue);
}
}
我可能想寫這樣的東西:
AtomicReference<String> ref = new AtomicReference<String>("hello");
List<CharSequence> texts = new ArrayList<>();
...
ref.fillIntoList(texts);
如果我的假設fillIntoList方法只是List<T>在無法編譯的簽名中包含。幸運的是,它確實可以編譯代碼。如果該Collections.fill方法沒有完成此操作<? super T>,那么Collections.fill在我的方法中呼叫該fillIntoList方法將會失敗。
任何這種情況的出現都是非常奇特的。但它可以出現。List<? super T>是這里嚴格的高級簽名 - 它可以做所有事情List<T>,甚至更多,而且它在語意上也是正確的:當然,我可以通過在每個插槽中寫入一個我肯定知道的東西的參考來填充 foo 串列bar,如果 bar 是 foo 的子級。
uj5u.com熱心網友回復:
那是因為繼承在某些情況下很有用。
例如,如果您有以下類結構:
public class Parent {
//some code
}
public class Child extends Parent {
//some another code
}
您可以使用第一種方法撰寫:
List<Child> children = new ArrayList<>();
Parent otherParentObject = new Parent(); //after this line, set the values for the class
List<Parent> outParentList = new ArrayList<>();
fill(children, otherParentObject); //fill method using first signature;
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/405253.html
標籤:
上一篇:如何正確創建方法接受物件串列
