Java 泛型萬用字元?解惑
T 有型別
? 未知型別
一、萬用字元的上界
既然知道List<Cat>並不是List<Anilmal>的子型別,那就需要去尋找替他解決的辦法, 是AnimalTrianer.act()方法變得更為通用(既可以接受List<Animal>型別,也可以接受List<Cat>等引數)。在java裡解決辦法就是使用萬用字元“?”,具體到AnimalTrianer,就是將方法改為act(List<? extends Animal> list),當中“?”就是萬用字元,而“? extends Animal”則表示萬用字元“?”的上界為Animal,換句話說就是,“? extends Animal”可以代表Animal或其子類,可代表不了Animal的父類(如Object),因為萬用字元的上界是Animal。如下,為改進之後的AnimalTrianer
public class AnimalTrainer { public void act(List<? extends Animal> list) { for (Animal animal : list) { animal.eat(); } } }
再來測試一下,如下,發現Test 2 可以通過編譯了:
public class TestAnimal { public static void main(String[] args) { AnimalTrainer animalTrainer = new AnimalTrainer(); //Test 1 List<Animal> animalList = newArrayList<>(); animalList.add(new Cat("cat1")); animalList.add(new Bird("bird1")); animalTrainer.act(animalList); //可以通過編譯 //Test 2 List<Cat> catList = new ArrayList<>(); catList.add(new Cat("cat2")); catList.add(new Cat("cat3")); animalTrainer.act(catList); //也可以通過編譯} }
經過上述分析,可以知道List<Animal>和List<Cat>都是List<? extends Animal>的子型別,類似有List<Bird>,List<Magpie>也是List<? extends Animal>的子型別。
現總結如下,對於萬用字元的上界,有以下幾條基本規則:(假設給定的泛型型別為G,(如List<E>中的List),兩個具體的泛型引數X、Y,當中Y是X的子類(如上的Animal和Cat))
- G<? extends Y> 是 G<? extends X>的子型別(如List<? extends Cat> 是 List<? extends Animal>的子型別)。
- G<X> 是 G<? extends X>的子型別(如List<Animal> 是 List<? extends Animal>的子型別)
- G<?> 與 G<? extends Object>等同,如List<?> 與List<? extends Objext>等同。
學到這裡,可能會遇到一些疑惑的地方,或者說事理解不透的地方,先觀察如下兩段程式碼片段,判斷一下其是否可行??
public void testAdd(List<? extends Animal> list){ //....其他邏輯 list.add(new Animal("animal")); list.add(new Bird("bird")); list.add(new Cat("cat")); }
List<? extends Animal> list = new ArrayList<>(); list.add(new Animal("animal")); list.add(new Bird("bird")); list.add(new Cat("cat"));
先分析如下:因為“? extends Animal”可代表Animal或其子類(Bird,Cat),那上面的操作應該是可行的。事實上是”不行“,即無法通過編譯。為什麼呢??
在解釋之前,再來重新強調一下已經知道的規則:在List<Aimal> list裡只能新增Animal類物件及其子類物件(如Cat和Bird物件),在List<Bird>裡只能新增Bird類和其子類物件(如Magpie),可不能新增Animal物件(不是Bird的子類),類似的在List<Cat>和List<Magpie>裡只能新增Cat和Bird物件(或其子類物件,不過這沒有列出)。現在再回頭看一下testAdd()方法,我們知道List<Animal>、List<Cat等都是List<? extends Animal>的子型別。先假設傳入的引數為為List<Animal>,則第一段程式碼的三個“add”操作都是可行的;可如果是List<Bird>呢??則只有第二個“add”可以執行;再假設傳入的是List<Tiger>(Tiger是想象出來的,可認為是Cat的子類),則三個“add”操作都不能執行。
現在反過來說,給testAdd傳入不同的引數,三個“add”操作都可能引發型別不相容問題,而傳入的引數是未知的,所以java為了保護其型別一致,禁止向List<? extends Animal>新增任意物件,不過卻可以新增null,即list.add(null)是可行的。有了上面談到的基礎,再來理解第二段程式碼就不難了,因為List<? extends Animal>的型別“? extends Animal”無法確定,可以是Animal,Bird或者Cat等,所以為了保護其型別的一致性,也是不能往list新增任意物件的,不過卻可以新增null。
先總結如下:不能往List<? extends Animal> 新增任意物件,除了null。
另外提醒大家注意的一點是,在List<? extends Animal> 可以是Animal類物件或Bird物件等(只是某一類物件),反過來說,在List<? extends Animal> list裡的都是Animal物件,即Bird也是Animal物件,Cat也是Animal物件(用java的語言來說就是子類可以指向父類,父類卻不能指向子類),那麼在Animal裡的所有方法都是可以呼叫的,如下:
for (Animal animal : list) { animal.eat(); }
二、萬用字元的下界
既然有了萬用字元的上界,自然有著萬用字元的下界。可以如此定義萬用字元的下界 List<? super Bird>,其中”Bird“就是萬用字元的下界。注意:不能同時宣告泛型萬用字元申明上界和下界。
在談注意細節之前,我們先看一下萬用字元的使用規則——對於萬用字元的上界,有以下幾條基本規則:(假設給定的泛型型別為G,(如List<E>中的List),兩個具體的泛型引數X、Y,當中Y是X的子類(如上的Animal和Cat))
- G<? super X> 是 G<? super Y>的子型別(如List<? super Animal> 是 List<? super Bird>的子型別)。
- G<X> 是 G<? super X>的子型別(如List<Animal> 是 List<? super Animal>的子型別)
現在再來看如下程式碼,判斷其是否符合邏輯:
public void testAdd(List<? super Bird> list){ list.add(new Bird("bird")); list.add(new Magpie("magpie")); }
List<? super Bird> list = new ArrayList<>(); list.add(new Bird("bird")); list.add(new Magpie("magpie")); list.add(new Animal("animal"));
看第一段程式碼,其分析如下,因為”? super Bird”代表了Bird或其父類,而Magpie是Bird的子類,所以上訴程式碼不可通過編譯。而事實上是”行“,為什麼呢?2?
在解疑之前,再來強調一個知識點,子類可以指向父類,即Bird也是Animal物件。現在考慮傳入到testAdd()的所有可能的引數,可以是List<Bird>,List<Animal>,或者List<Objext>等等,發現這些引數的型別是Bird或其父類,那我們可以這樣看,把bird、magpie看成Bird物件,也可以將bird、magpie看成Animal物件,類似的可看成Object物件,最後發現這些新增到List<? supe Bird> list裡的物件都是同一類物件(如本文剛開篇提到的Test 1),因此testAdd方法是符合邏輯,可以通過編譯的。:
現在再來看一下第二段程式碼對於,第二、三行程式碼的解釋和上文一樣,至於最後一行“list.add(new Animal("animal"))”是無法通過編譯的,為什麼的??為了保護型別的一致性,因為“? super Bird”可以是Animal,也可以是Object或其他Bird的父類,因無法確定其型別,也就不能往List<? super Bird>新增Bird的任意父類物件。
既然無法確定其父類物件,那該如何遍歷List<? super Bird> ? 因為Object是所有類的根類,所以可以用Object來遍歷。如下,不過貌似其意義不大。
for (Object object : list) {//...}
那“? super BoundingType”可以應用在什麼地方呢??“? super BoundingType”應用相對廣泛,只不過是混合著用。下面舉個簡單的例子。先假設有以下兩個Student和CollegeStudent,當中CollegeStudent繼承Student,如下:
public class Student implements Comparable<Student>{ private int id; public Student(int id) { this.id = id; } @Override public int compareTo(Student o) { return (id > o.id) ? 1 : ((id < o.id) ? -1 : 0); } }
public class CollegeStudent extends Student{ public CollegeStudent(int id) { super(id); } }
先需要根據他們的id對他們進行排序(注意此處是對陣列物件進行排序),設計方法如下,(n指陣列元素的個數):
public static <T extends Comparable<? super T>> void selectionSort(T[] a,int n)
先理解此方法含義,首先<T extends Comparable<T>>規定了陣列中物件必須實現Comparable介面,Comparable<? Super T>表示如果父類實現Comparable介面,其自身可不實現,如CollegeStudent。先假設有一個CollegeStudent的陣列,如下:
CollegeStudent[] stu = new CollegeStudent[]{ new CollegeStudent(3),new CollegeStudent(2), new CollegeStudent(5),new CollegeStudent(4)};
執行方法 selectionSort(stu,4)是完全可以通過的。可如果定義的selectionSort方法如下:
public static <T extends Comparable<T>> void selectionSort(T[] a,int n)
則方法selectionSort(stu,4)不能執行,因為CollegeStudent沒有實現Comparable<CollegeStudent>介面。換句話就是“? super T”使selectionSort方法變得更為通用了。selectionSort完整程式碼的實現可參考本文的末尾。
三、無界萬用字元
知道了萬用字元的上界和下界,其實也等同於知道了無界萬用字元,不加任何修飾即可,單獨一個“?”。如List<?>,“?”可以代表任意型別,“任意”也就是未知型別。無界萬用字元通常會用在下面兩種情況:
1、當方法是使用原始的Object型別作為引數時,如下:
public static void printList(List<Object> list) { for (Object elem : list) System.out.println(elem + ""); System.out.println(); }
可以選擇改為如下實現:
public static void printList(List<?> list) { for (Object elem: list) System.out.print(elem + ""); System.out.println(); }
這樣就可以相容更多的輸出,而不單純是List<Object>,如下:
List<Integer> li = Arrays.asList(1, 2, 3); List<String> ls = Arrays.asList("one", "two", "three"); printList(li); printList(ls);
2、在定義的方法體的業務邏輯與泛型型別無關,如List.size,List.cleat。實際上,最常用的就是Class<?>,因為Class<T>並沒有依賴於T。
最後提醒一下的就是,List<Object>與List<?>並不等同,List<Object>是List<?>的子類。還有不能往List<?> list裡新增任意物件,除了null。
附錄:selectionSort的程式碼實現:(如果需要實現比較好的輸出,最好重寫Student的toString方法)
public class SortArray { //對一組陣列物件運用插入排序,n指陣列元素的個數 public static <T extends Comparable<? super T>> void selectionSort(T[] a,int n) { for (int index = 0; index < n-1; index++) { int indexOfSmallest = getIndexOfSmallest(a,index,n-1); swap(a,index,indexOfSmallest); } } public static <T extends Comparable<? super T>> int getIndexOfSmallest(T[] a, int first, int last) { T minValue = a[first]; // 假設第一個為minValue int indexOfMin = first; // 取得minValue的下標 for (int index = first + 1; index <= last; index++) { if (a[index].compareTo(minValue) < 0) { minValue = a[index]; indexOfMin = index; } } return indexOfMin; } public static void swap(Object[] a,int first,int second) { Object temp = a[first]; a[first] = a[second]; a[second] = temp; } public static void main(String[] args) { CollegeStudent[] stu = new CollegeStudent[]{ new CollegeStudent(3), new CollegeStudent(2), new CollegeStudent(5), new CollegeStudent(4)}; selectionSort(stu, 4); for (Student student : stu) { System.out.println(student); } } }