淺析Java泛型中extends和super作用


一、前言

  最近依然在看《Java編程思想》這本書,說實話,非常晦澀難懂,除了講的比較深入外,翻譯太爛也是看不懂的一個重要原因。今天在看泛型這一章,也算是有些收獲吧,所以寫篇博客,記錄一下其中比較容易遺忘的一個知識點:在泛型中,extends和super關鍵字的含義和用法


二、描述

  學過Java的人應該都知道,extends和super這兩個關鍵字的最常見的用法:

  • extends:讓一個類繼承另外一個類;
  • super:指向父類對象的引用;

  但是在泛型中,這兩個關鍵字都被重載,有了新的含義和用法;


三、解析

 1、extends在泛型中的基本使用

  我們通過一段代碼進行講解:

// 在泛型中使用extends:<T extends Number>
public class Test <T extends Number> {

	public static void main(String[] args) {
		// 正確使用:Number、Integer、Double、Byte 均屬於 Number
		Test<Number> t = new Test<>();
		Test<Integer> t1 = new Test<>();
		Test<Double> t2 = new Test<>();
		Test<Byte> t3 = new Test<>();
		
		// 錯誤使用:String不屬於Number
//		Test<String> t4 = new Test<>();
	}
}

  看上面的代碼,我們聲明了一個類Test,它有一個泛型T,T的聲明為<T extends Number>,這表示什么意思呢?這表明:類Test的泛型,只能是Number類型,或者Number類型的派生類型(子類型);所以,在下面的main方法中,我們將Test類對象的泛型定為NumberIntegerDoubleByte均沒有問題,因為它們是Number本身,或者Number的子類型;但是我們將泛型定義為String類型,就會編譯錯誤,因為String不是Number類型的派生類。所以,泛型中extends關鍵字的作用就是:限定泛型的上邊界


 2、Java泛型中的通配符

  上面講解了泛型中,extends關鍵字最基本的用法,比較簡單,但是在實際的使用中,還有一種更加復雜的用法,就是搭配泛型中的通配符 ? 使用,所以我先來簡單的介紹一下泛型中的通配符—— ?

  在泛型中的通配符就是一個問號,標准叫法是無界通配符,它一般使用在參數或變量的聲明上:

// 在參數中使用無界通配符
public static void test(List<?> list) {
    Object o = list.get(1);
}

public static void main(String[] args) {
    List<Integer> list1 = new ArrayList<Integer>();

    // 在變量聲明中使用無界通配符
    List<?> list2 = list1;

    test(list1);
    test(list2);
}

  泛型中使用無界通配符,表示泛型可以是任意具體的類型,沒有限制(基本數據類型除外,基本數據類型不能用作泛型,可以使用基本數據類型的包裝類);所以無界通配符給人的感覺就和原生的類型沒什么區別,比如就上面這段代碼,使用List<?>,和直接使用List,好像是一樣的;但是實際上還是有一些區別的,比如看下面這段代碼:

// 在參數中使用無界通配符
public static void test1(List<?> list) {
    // 均編譯錯誤,因為使用了無界通配符,編譯器無法確定具體是什么類型
    // list.add(1111);
    // list.add("aaa");
    // list.add(new Object());
}
// 在參數中使用原生List
public static void test2(List list) {
    // 編譯通過,不加泛型時,編譯器默認為Object類型
    list.add(1111);
    list.add("aaa");
    list.add(new Object());
}

public static void main(String[] args) {
    // 聲明兩個泛型明確的list集合
    List<Integer> list1 = new ArrayList<>();
    List<String> list2 = new ArrayList<>();
	// 調用使用了<?>的方法
    test(list1);
    test(list2);
}

  上面這段代碼演示了使用通配符和原生類型的區別。在方法test1中,使用了泛型類型為通配符的List,此時,我們將無法使用List的add方法,為什么?我們先看一下add方法的聲明:

boolean add(E e);

  我們可以看到,add方法的參數類型是一個泛型,可是在test1中,我們在泛型中使用了通配符,這意味着list的泛型可以是任意類型,編譯器並不知道它具體是哪種類型,所以不允許我們調用list中的泛型方法。這時候大家可能有點疑問,為什么Object類型也不行呢,Object類型不是所有類型的父類嗎。那是因為Java對於原生類型和通配符有不一樣的定義,而在語法的設計上要符合這種定義

  在《Java編程思想》上描述了使用通配符泛型和原生類型在定義上的區別:

  • List:表示可以存儲任意Object類型的集合;
  • List<?>:表示一個存儲某種特定類型的List集合,但是不知道這種特定類型是什么;

  從上面對兩種定義的描述,我們可以大致了解通配符與原生類型的區別;當然,具體其實還要更加復雜,但是我現在着重講的是泛型中的extendssuper關鍵字,所有這里就不贅述了,上面的講解主要是為了引出下面的內容。下面開始講解這兩個關鍵字如何搭配通配符使用。


 3、extends關鍵字搭配?使用

  上面講解了extends的一個簡單用法,現在來講解一個更加復雜的用法,就是extends關鍵字搭配無界通配符?使用。首先還是一樣,來看一段代碼:

public static void test1(List<? extends Number> list) {
    Number number = list.get(1);
    // 下列均編譯錯誤:list中元素的類型可以是任意Number的子類,所有無法確定list存儲的具體是哪一種類型
    //	list.add(11);
    //	list.add(new Integer(1));
    //	list.add(new Double(1));
}

public static void test2(List list) {
    Object object = list.get(1);
    // 編譯通過:原生list可以存儲任意Object類型
    list.add(11);
    list.add(new Integer(1));
    list.add(new Double(1));
}

public static void main(String[] args) {
    // 注意下列List的泛型
    List<String> list1 = new ArrayList<>();
    List<Integer> list2 = new ArrayList<>();
    List<Double> list3 = new ArrayList<>();
    List<Number> list4 = new ArrayList<>();

    // 調用使用了泛型的方法
    test1(list1);	// 編譯錯誤,因為list1的泛型為String,不是Number的子類
    test1(list2);	// 編譯通過,因為list2的泛型為Integer,是Number的子類
    test1(list3);	// 編譯通過,因為list3的泛型為Double,是Number的子類
    test1(list4);	// 編譯通過,因為list4的泛型為Number,是Number的本身
}

  我們通過上面這段代碼進行講解。上面我們定義了一個方法,名字叫test1,它接收一個參數List<? extends Number> list,這表示參數是一個List類型,而且這個List類型的泛型不確定,但是只能是Number類型,或者Number類型的子類,所以我們在main方法中創建了四個List對象,泛型分別是NumberIntegerDoubleString,只有String類型的list作為參數調用test1時,才編譯錯誤,因為String不是Number類型的子類;所以,此處extends的作用是:限定了參數或變量中,泛型的上界

  這種寫法有什么好處呢?好處就是:我們確定了泛型的上界,縮小了類型的范圍,例如test1中,我們取出List集合中的元素,返回值是一個Number,而不是像test2方法中,返回值是Object類型。這是因為我們使用extends,限制了泛型的類型是Number或其子類,於是編譯器就可以知道,這個list中的所有元素,一定屬於Number,所有可以用Number接收,也可以調用Number類的方法;但是在test2方法中,沒有限定類型上界,所有只能用Object接收;

  那這么寫有什么問題呢?也很明顯,就是我們在講通配符時說到的問題:無法調用參數為泛型的方法。我們使用了通配符,同時繼承了Number類,根據我們之前說過的定義,List<? extends Number> list表示一個存儲特定類型的List集合,且這個類型是Number或者Number的子類,這就是Java給這種參數的定義。所以編譯器只能知道,這個list中,元素的大致類型,但是它具體是哪種類型,編譯器不知道,所以編譯器不允許我們調用任何需要用到這個具體類型信息的方法;比如我們傳入一個元素為Byte類型的List,然后再調用add方法,為集合加入一個int值,這顯然是不合理的,雖然它們都是Number的子類。所以,使用這種參數類型,有時候也可以幫助我們限定某些不應該進行的操作。


 4、super關鍵字搭配?使用

  super關鍵字和extends關鍵字的含義相反,super關鍵字的作用是:限定了泛型的下界;還是先看一段代碼:

public static void test1(List<? super Integer> list) {
    // 只能通過Object接收
    Object object = list.get(1);

    // 編譯正確,允許以下操作
    list.add(111);
    list.add(new Integer(1));
    // 編譯錯誤,1.5不是Integer
	list.add(1.5);
}

public static void main(String[] args) {
    // 創建三個用於測試的List集合,泛型不同
    List<Integer> list1 = new ArrayList<>();
    List<Number> list2 = new ArrayList<>();
    List<Double> list3 = new ArrayList<>();

    // 調用使用了泛型的方法
    test1(list1);	// 編譯正確,因為list1的泛型為Integer,等價於參數中泛型的下界
    test1(list2);	// 編譯正確,因為list2的泛型為Number,是Integer的基類
    test1(list3);	// 編譯錯誤,因為list1的泛型為Double,不是Integer的基類
}

  上面的代碼中定義了一個方法test1來測試泛型中的super關鍵字,這個方法的參數類型是List<? super Integer> list,這表示這個方法的參數是一個List集合,而集合的泛型只能是Integer,或者Integer的基類。我們在main方法中定義了三個集合驗證這個結論,這三個集合的泛型各不相同,分別使用這三個集合作為參數,調用test1方法。結果,泛型為Integer,以及Number的集合,調用方法成功,而泛型為Double的list編譯錯誤。

  我們看test1中的代碼可以發現,與泛型中使用extends關鍵字不同,使用super關鍵字可以調用add這個參數為泛型的方法,這是為什么呢?因為我們在泛型中使用了super這個,限定了泛型的下界為Integer,這表示在list這個集合中,所有的元素一定是Integer類型,或者Integer類型的基類型,比如說Number;這表明,我們在集合中添加一個Integer類型的元素,一定是合法的,因為Integer類型的對象,肯定也是一個Integer的基類型的對象(多態);當然,如果Integer還有子類,那也可以在add中傳入Integer的子類對象(雖然Integer沒有子類);

  但是我們從這個list中取出元素,只能用Object接收,這是為什么呢?因為我們定義了list的泛型下界是Integer,表明list的具體泛型可以是Integer的任何基類,而一個類的基類不止一個,比如Integer繼承Number,而Number又繼承Object。在這種情況下,編譯器並不知道泛型具體是哪一種類型,所以只能用最高類Object進行接收。


四、總結

  上面的內容大致的講解了一下泛型中extends和super關鍵字的用法,讓人可以有一個簡單的認識,但是更多的是我個人的理解。通過看書,我覺得泛型是一個很復雜的東西,僅僅只是看還是不夠的,還是需要多多實踐,在實踐中才能加深理解。

參考

《Java編程思想》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM