corejava基礎知識(4)-萬用字元

Zouxxyy發表於2019-03-13

萬用字元

泛型的侷限性與漏洞

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 ,新增類強制型別轉換後可以呼叫,這與普通類繼承規則一樣

複製程式碼

完整程式碼:繼承示例

  1. 無論 S 和 T 有什麼關係,Pair<S>Pair<T>沒有什麼聯絡。
  2. 泛型與原始內容相容,但是原始內容裡的型別引數這個物件無法呼叫方法
  3. 泛型裡的型別引數可以繼承,和類的繼承規則一樣

? 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); }
複製程式碼

示例

完整程式碼:萬用字元示例