程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 關於泛型的一些細節,泛型一些細節

關於泛型的一些細節,泛型一些細節

編輯:JAVA綜合教程

關於泛型的一些細節,泛型一些細節


  之前在寫公司項目的底層框架的時候用到一些泛型,實踐中涉及到一些沒關注到的細節,為此專門去Oracle官網把泛型的文檔學習了一遍。

  Java中的泛型跟C++裡面的Template(模板)是同一個類型的東西,都是為了在其他地方調用的時候可以傳入各種參數類型。

  在實踐中,與使用泛型有相似效果的是函數重載,即根據傳入參數類型的不同,選擇調用不同的函數。泛型和函數重載各有利弊,需要根據使用情景來選擇。如果一段代碼對於不同類型的參數,可以不做類型區分地使用,比如List的add方法,這時就用泛型。而如果一段代碼對於傳入的參數,應該根據不同的數據類型,執行不同的語句,這時就應該使用重載。為什麼?因為這時如果使用泛型,就會出現大量的instanceof判斷,判斷之後還有各種影響代碼質量的泛型與實際類型之間的類型強轉,而如果返回值也是泛型,那就更麻煩了。典型的就是之前對SharedPreferences進行封裝,對於不同類型的參數執行統一的get/put方法,但是如果傳入String類型,底層就要執行getString/putString方法,如果傳入int,就要執行getInt/putInt方法,這樣就必須使用如下的函數重載形式:

    public static String get(String key, String defaultValue) {
        SharedPreferences sp = obtainPref();
        return sp.getString(key, defaultValue);
    }

    public static int get(String key, int defaultValue) {
        SharedPreferences sp = obtainPref();
        return sp.getInt(key, defaultValue);
    }

    public static boolean get(String key, boolean defaultValue) {
        SharedPreferences sp = obtainPref();
        return sp.getBoolean(key, defaultValue);
    }    

 另外像JDK源碼裡面,StringBuilder的append方法,也是根據參數類型寫了一大堆看似啰嗦的重載函數,為什麼?因為方法體不一樣啊。

 

  回歸正題,如果針對不同的參數類型,可以用同一段代碼,還是推薦用泛型的,畢竟可以把幾段代碼合並成一段代碼。

public class MyClass<T> {

    public static void main(String[] args) {
        MyClass<Integer> myClass = new MyClass<>();
        myClass.printT(100);
        MyClass<Boolean> myClass2 = new MyClass<>();
        myClass2.printT(true);
    }

    public void printT(T t) {
        System.out.print(t);
    }
}

   請注意,這裡的泛型"T",代表的只能是Object類型,不能是int,boolean,char這些基本數據類型,比如像下面這樣寫就是錯的:

MyClass<int> myClass = new MyClass<>();  //報錯
myClass.printT(1);

 

  也就是說,其實T是繼承自Object的。

  那麼,為什麼定義的時候泛型參數必須是Object,而實際傳值的時候可以是100,true這些呢?因為JDK在編譯時做了一個自動裝箱的處理,把int類型包裝成了Integer類型,boolean類型則包裝成Boolean類型。可以參考我的另一篇blog: Java暗箱操作之自動裝箱與拆箱

  代碼裡面每一個用到泛型參數 T, K, E,...都必須遵循先聲明再使用的原則,即如果你提到了這些泛型名稱,就必須在之前的某個地方被聲明過,否則會報錯。

  泛型的聲明位置只能是兩個地方,一是類名處,二是方法處,別的地方都不能聲明。第一種方式,就是上面的 public class MyClass<T> {..}這種,在類名之後加,這樣在類裡面所有地方都能用"T"這個泛型參數。第二種方式在方法處聲明可能不太常見,之前我也不太熟悉,但項目裡確實用到了,只好研究一下,聲明格式類似於這樣:

public <T> T printT(T t) {
    System.out.print(t);
}

 

  這裡的T就只能作用於方法體了,而且會覆蓋類上聲明的泛型,例如以下代碼會正常運行:

public class MyClass<T> {

    public static void main(String[] args) {
        MyClass<String> myClass = new MyClass<>();
        myClass.printT(100);
    }

    public <T> T printT(T t) {
        System.out.print(t);
        return t;
    }

}

 

   調用時,類上的泛型是String,方法上傳入的是Integer,那就以方法上的為准咯~

  特別注意,方法上的泛型參數必須聲明在返回值之前,public/private之後,是有固定位置的。

   

  可以對調用時傳入的泛型加限制條件,限制T必須是某個類(接口)的子類

public class MyClass<T extends Number> {...}

  這裡,T就只能是Number或者Number的子類Integer,Float,Long這些,傳入String就是錯誤的。

  T也可以繼承自多個類,注意這裡的類是泛指,包括接口在內,即寫成

public class MyClass<T extends A & B & C> {...}

 

  其中A可以是類或接口,B、C只能是接口,即多繼承的話至多只能有一個是類,且必須把類寫在第一個。

 

  傳入的泛型參數還可以是wildcard(通配符)

MyClass<?> myClass = new MyClass<>();

 

   "?"是在調用時傳入的東西,取代String, Integer這些實際的類型。

  有兩種情況會傳入"?":1、調用過程中僅涉及到Object的方法,像equals()等等;2、調用過程中不依賴於泛型。最典型的是Class<?>,因為調用的Class方法基本用不到泛型。

  

  更多內容參考Oracle官方文檔:Generics

  

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved