菜鳥先飛之JAVA_泛型上下界詳談
阿新 • • 發佈:2018-12-21
這兩天在接觸泛型的時候,發現這個泛型的上下界比較抽象,不好理解。經過查閱資料,解決了這個問題,現在分享一下心得。
泛型上下界的介紹 ?exdends E:接收E型別或者E的子型別物件,上界。 ?super E:接收E型別或者E的父型別,下界。
上下界的使用場景 一般在儲存元素的時候都是用上界,因為這樣取出都是按照上界型別來運算的。不會出現型別的安全隱患。 通常對集合中的元素進行取出操作時,可以是用下界。
泛型上界使用舉例 首先定義3個been,分別是Animal、Dog、Teddy。其中Dog繼承了Animal,Teddy繼承了Dog。
通過查詢API我們可以知道,在Collection介面中有這麼一個方法。 boolean addAll(Collection<? extends E> c) 將指定 collection 中的所有元素都新增到此 collection 中(可選操作)。
通過上面的結果我們可以看出來,Collection集合的addAll方法接收的引數型別泛型被? extends E限定之後,只能接收E和E的子類物件。如果別的型別的物件要新增進這個集合,就會在編譯時報錯。這樣限定後就能確保,集合中只有我們想要的物件型別。
泛型下界使用舉例 我們還使用上面的3個been。然後我們在API中找到TreeSet,發現它有這麼一個構造方法。 public TreeSet(Comparator<? super E> comparator) 構造一個新的空 TreeSet,它根據指定比較器進行排序。插入到該 set 的所有元素都必須能夠由指定比較器進行相互比較。 那我們就先建立一個比較器。根據物件的name進行排序。
呼叫比較器排序
通過上面的結果我們可以看出來,Dog物件集合也能通過CompByName這個比較器進行比較,所以當比較器被? super E限定之後,這個比較器既可以提供給Dog和Dog的子類去做比較。當看到這個地方的時候肯定會有疑問,這個限定下限和限定上限有什麼區別。接下來我們說一下java中的PECS法則。
PECS法則的概述 PECS指“Producer Extends,Consumer Super”。用我們的說,如果引數化型別表示一個生產者,就使用<? extends E>;如果它表示一個消費者,就使用<? super E>。
<? extends E>上界萬用字元 這個拿到我們的demo1中來說,就是Collection<? extends Dog>,意思就是Collection這個集合可以存放Dog類以及Dog類的所有子類。所以在dogList中新增animalList的全部內容的時候,會在編譯的時候報錯。
<? super E>下界萬用字元 這個拿到我們demo2中來說,就是Comparator<? super E> comparator,對於dogSet來說只能用(Comparator<? super Dog> comparator),呼叫的是自己的比較器所以通過,但是對於teddySet來說只能用(Comparator<? super Teddy> comparator),呼叫的是自己父類的比較器所以通過,對於animalSet來說只能用(Comparator<? super Animal> comparator),呼叫的是自己子類的比較器,所以會在編譯的時候報錯。
PECS法則總結 1、如果要從集合中讀取型別E的資料,並且不能寫入,可以使用 ? extends 萬用字元;(Producer Extends) 2、如果要從集合中寫入型別E的資料,並且不需要讀取,可以使用 ? super 萬用字元;(Consumer Super) 3、如果既要存又要取,那麼就不要使用任何萬用字元。
泛型上下界的介紹 ?exdends E:接收E型別或者E的子型別物件,上界。 ?super E:接收E型別或者E的父型別,下界。
上下界的使用場景 一般在儲存元素的時候都是用上界,因為這樣取出都是按照上界型別來運算的。不會出現型別的安全隱患。 通常對集合中的元素進行取出操作時,可以是用下界。
泛型上界使用舉例 首先定義3個been,分別是Animal、Dog、Teddy。其中Dog繼承了Animal,Teddy繼承了Dog。
public class Animal { private String name; private Integer age; public Animal(String name, Integer age) { super(); this.name = name; this.age = age; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "[name=" + name + " , age=" + age + "]"; } } class Dog extends Animal { public Dog(String name, Integer age) { super(name, age); } } class Teddy extends Dog { public Teddy(String name, Integer age) { super(name, age); } }
通過查詢API我們可以知道,在Collection介面中有這麼一個方法。 boolean addAll(Collection<? extends E> c) 將指定 collection 中的所有元素都新增到此 collection 中(可選操作)。
public class demo1 { /* * 限定上界 * ?extends E * * boolean addAll(Collection<? extends E> c) * 將指定 collection 中的所有元素都新增到此 collection 中(可選操作)。 */ public static void main(String[] args) { ArrayList<Animal> animalList = new ArrayList<>(); animalList.add(new Animal("animal1",11)); animalList.add(new Animal("animal2",12)); animalList.add(new Animal("animal3",13)); ArrayList<Dog> dogList = new ArrayList<>(); dogList.add(new Dog("dog1",21)); dogList.add(new Dog("dog2",22)); dogList.add(new Dog("dog2",23)); ArrayList<Teddy> teddyList = new ArrayList<>(); teddyList.add(new Teddy("teddy1",31)); teddyList.add(new Teddy("teddy2",32)); teddyList.add(new Teddy("teddy3",33)); ArrayList<String> stringList = new ArrayList<>(); stringList.add("str1"); stringList.add("str1"); stringList.add("str1"); // dogList.addAll(animalList);// 報錯The method addAll(Collection<? extends Dog>) in the type ArrayList<Dog> is not applicable for the arguments (ArrayList<Animal>) // dogList.addAll(stringList);// 報錯The method addAll(Collection<? extends Dog>) in the type ArrayList<Dog> is not applicable for the arguments (ArrayList<String>) dogList.addAll(teddyList); System.out.println(dogList.size()); // 結果是6 } }
通過上面的結果我們可以看出來,Collection集合的addAll方法接收的引數型別泛型被? extends E限定之後,只能接收E和E的子類物件。如果別的型別的物件要新增進這個集合,就會在編譯時報錯。這樣限定後就能確保,集合中只有我們想要的物件型別。
泛型下界使用舉例 我們還使用上面的3個been。然後我們在API中找到TreeSet,發現它有這麼一個構造方法。 public TreeSet(Comparator<? super E> comparator) 構造一個新的空 TreeSet,它根據指定比較器進行排序。插入到該 set 的所有元素都必須能夠由指定比較器進行相互比較。 那我們就先建立一個比較器。根據物件的name進行排序。
public class CompByName implements Comparator<Dog> {
@Override
public int compare(Dog a1, Dog a2) {
int num = a1.getName().compareTo(a2.getName()); //比較名字的大小
return num == 0 ? a1.getAge()-a2.getAge() : num; //如果名字相同比較年齡
}
}
呼叫比較器排序
public class demo2 {
public static void main(String[] args) {
TreeSet<Teddy> teddySet = new TreeSet<>(new CompByName());
teddySet.add(new Teddy("abc",11));
teddySet.add(new Teddy("aac",12));
teddySet.add(new Teddy("abc",13));
TreeSet<Dog> dogSet = new TreeSet<>(new CompByName());
dogSet.add(new Dog("bbc",21));
dogSet.add(new Dog("bac",22));
dogSet.add(new Dog("bbc",23));
// TreeSet<Animal> animalSet = new TreeSet<>(new CompByName());//報錯Type mismatch: cannot convert from TreeSet<Dog> to TreeSet<Animal>
// animalSet.add(new Animal("animal1",1));
// TreeSet<String> stringSet = new TreeSet<>(new CompByName());//報錯Type mismatch: cannot convert from TreeSet<Animal> to TreeSet<String>
// stringSet.add("str1");
System.out.println(teddySet);//結果:[[name=aac , age=12], [name=abc , age=11], [name=abc , age=13]]
System.out.println(dogSet);//結果:[[name=bac , age=22], [name=bbc , age=21], [name=bbc , age=23]]
}
}
通過上面的結果我們可以看出來,Dog物件集合也能通過CompByName這個比較器進行比較,所以當比較器被? super E限定之後,這個比較器既可以提供給Dog和Dog的子類去做比較。當看到這個地方的時候肯定會有疑問,這個限定下限和限定上限有什麼區別。接下來我們說一下java中的PECS法則。
PECS法則的概述 PECS指“Producer Extends,Consumer Super”。用我們的說,如果引數化型別表示一個生產者,就使用<? extends E>;如果它表示一個消費者,就使用<? super E>。
<? extends E>上界萬用字元 這個拿到我們的demo1中來說,就是Collection<? extends Dog>,意思就是Collection這個集合可以存放Dog類以及Dog類的所有子類。所以在dogList中新增animalList的全部內容的時候,會在編譯的時候報錯。
<? super E>下界萬用字元 這個拿到我們demo2中來說,就是Comparator<? super E> comparator,對於dogSet來說只能用(Comparator<? super Dog> comparator),呼叫的是自己的比較器所以通過,但是對於teddySet來說只能用(Comparator<? super Teddy> comparator),呼叫的是自己父類的比較器所以通過,對於animalSet來說只能用(Comparator<? super Animal> comparator),呼叫的是自己子類的比較器,所以會在編譯的時候報錯。
PECS法則總結 1、如果要從集合中讀取型別E的資料,並且不能寫入,可以使用 ? extends 萬用字元;(Producer Extends) 2、如果要從集合中寫入型別E的資料,並且不需要讀取,可以使用 ? super 萬用字元;(Consumer Super) 3、如果既要存又要取,那麼就不要使用任何萬用字元。