萬用字元
泛型的侷限性與漏洞
Pair<Employee> employeePair= new Pair<>(new Employee("員工1"), new Employee("員工2"));
Pair<Manager> managerPair= new Pair<>(new Manager("經理1", 100), new Manager("經理2", 200));
//測試1
//employeePair = managerPair; //錯誤, `Pair<S>`和`Pair<T>`沒有什麼聯絡。
//managerPair = employeePair // 錯誤, `Pair<S>`和`Pair<T>`沒有什麼聯絡。
//測試2
Pair pair = employeePair; // 泛型與原始內容相容,但是,看下面
//pair.getFirs t().getName(); // error, 是Employee類,但是不能呼叫方法!
//測試3
employeePair.setFirst(new Manager("經理3", 300)); // employeePair裡經理竟然和員工組成一對!
System.out.println(employeePair.getFirst().getName()); // 經理3
System.out.println(employeePair.getSecond().getName()); // 員工2
// System.out.println(((employeePair.getFirst())).getSalary()); // error
System.out.println(((Manager)(employeePair.getFirst())).getSalary()); // 300.0 ,新增類強制型別轉換後可以呼叫,這與普通類繼承規則一樣
複製程式碼
完整程式碼:繼承示例
- 無論 S 和 T 有什麼關係,
Pair<S>
和Pair<T>
沒有什麼聯絡。 - 泛型與原始內容相容,但是原始內容裡的型別引數這個物件無法呼叫方法
- 泛型裡的型別引數可以繼承,和類的繼承規則一樣
? extends Object(上邊界限定萬用字元)
可以看出來原始泛型遇上繼承時會有些漏洞,比如會出現經理員工在同一Pair
的情況。於是Java專家引入了型別萬用字元 ?
我們把剛剛的第一行改為:
Pair<? extends Employee> employeePair= new Pair<>(new Employee("員工1"), new Employee("員工2"));
複製程式碼
此時,如果再向裡面新增Manager
時就會發生錯誤:
employeePair.setFirst(new Manager("經理3", 300)); // 錯誤
employeePair.setFirst(new Employee("員工")); // 錯誤,甚至新增員工都不行
複製程式碼
但是訪問可以:
Employee employee = employeePair.getFirst(); // 正確
複製程式碼
分析
首先永遠記住只能超類接收子類!!!反過來不行!!!(這個可以解釋下面的一切)
型別擦除 後 Pair<? extends Employee>
的方法有:
? extends Employee getFirst() {...} // 訪問器
void setFirst(? extends Employee) {...} // 更改器
複製程式碼
- 訪問器的返回值是
? extends Employee
,可以用子類Employee
接收 - 更改器的接收值是
? extends Employee
,極端情況是Employee
的最下面的子類
,而最下面的子類
只能接收更下面的子類(無),因此 拒絕接收任何特定的型別!
小結
簡單說就是:
可以 Employee
<-- ? extends Employee
,但是反過來不行!
所以這就是大家說的使用? extends Object
可以 安全的訪問泛型物件。我的理解核心是:如果T
作為返回值,用? extends Object
更安全。
? super Object(下邊界限定萬用字元)
這個和上面正好相反,更改器能用,訪問器不能用。
分析
? super Employee getFirst() {...} // 訪問器
void setFirst(? super Employee) {...} // 更改器
複製程式碼
- 訪問器的返回值是
? super Employee
,極端情況是Object
,只能用Object
接收 - 更改器的接收值是
? super Employee
,可以接收Employee 和 它的子類
小結
簡單說就是:
可以 ? super Employee
<-- Employee
,但是反過來不行!
所以這就是大家說的使用? super Object
可以 安全的更改泛型物件。我的理解核心是:如果T
作為方法引數,用? super Object
更安全。
共同特點
右邊的值傳給左邊接收:
? super Employee
<-- Employee
<-- ? extends Employee
是不是完全符合 只能超類接收子類,知道原理記起來就簡單。
例子
舉書上的一個例子作為練習:
public static <T extends Comparable<? super T>> T min(T[] a) //計算T[]的最小值
複製程式碼
理解:
T extends...
好理解,就是T
要實現後面的介面。- 實現
Comparable<? super T>
介面裡的方法int compareTo(? super T)
;此時型別
作為 方法引數,所以用? super
更安全。
?(無限定萬用字元)
? getFirst() {...} // 訪問器
void setFirst(?) {...} // 更改器
複製程式碼
- 訪問器只能用
Object
接收 - 更改器不能用
可以用任意Object
物件呼叫原始 Pair
類的setObject
方法,說白了就是什麼型別都能
作為泛型方法的方法引數,但就是不能有返回值。
萬用字元捕獲
由於萬用字元不能作為型別變數,所以必要時可以用一個輔助的泛型方法。
第一步:輔助泛型方法
public static <T> void swapHelper(Pair<T> p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
複製程式碼
第二步:萬用字元捕獲
public static void swap(Pair<?> p){ swapHelper(p); }
複製程式碼
示例
完整程式碼:萬用字元示例