1. 程式人生 > >菜鳥先飛之JAVA_泛型上下界詳談

菜鳥先飛之JAVA_泛型上下界詳談

這兩天在接觸泛型的時候,發現這個泛型的上下界比較抽象,不好理解。經過查閱資料,解決了這個問題,現在分享一下心得。
泛型上下界的介紹 ?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、如果既要存又要取,那麼就不要使用任何萬用字元。