一、 什么是泛型?
泛 型(Generic type 或者 generics)是對 簡單的理解,就是對類型的參數化,比如我們定義一個類屬性或者實例屬性時,往往要指定具體的類型,如Integer、Person等等, 但是如果使用了泛型,我們把這些具體的類型參數化,用一個廣泛的可以表示所有類型的“類型”T來定義,那這個T就是泛型的表示。
可以在集合框架(Collection framework)中看到泛型的動機。例如,Map 類允許您向一個 Map 添加任意類的對象,即使最常見的情況是在給定映射(map)中保存某個特定類型(比如 String)的對象。
因為 Map.get() 被定義為返回 Object,所以一般必須將 Map.get() 的結果強制類型轉換為期望的類型,如下面的代碼所示:
- Map m = new HashMap();
- m.put("key", "value");
- String s = (String) m.get("key");
二、 泛型的好處
Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了大翻修,所以許多重要的類,比如集合框架,都已經成為泛型化的了。這帶來了很多好處:1、 類型安全。 泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。
2、 消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。
3、 潛在的性能收益。 泛型為較大的優化帶來可能。在泛型的初始實現中,編譯器將強制類型轉換(沒有泛型的話,程序員會指定這些強制類型轉換)插入生成的字節碼中。
3.1 我們再程序中定義一個類,並制定泛型參數
- class Point<T>{ // 此處可以隨便寫標識符號,T是type的簡稱
- private T var ; // var的類型由T指定,即:由外部指定
- public T getVar(){ // 返回值的類型由外部決定
- return var ;
- }
- public void setVar(T var){ // 設置的類型也由外部決定
- this.var = var ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Point<String> p = new Point<String>() ; // 里面的var類型為String類型
- p.setVar("MLDN") ; // 設置字符串
- System.out.println(p.getVar().length()) ; // 取得字符串的長度
- }
- };
說明:
1. 命名類型參數
推薦的命名約定是使用大寫的單個字母名稱作為類型參數。這與 C++ 約定有所不同(參閱 附錄 A:與 C++ 模板的比較),並反映了大多數泛型類將具有少量類型參數的假定。對於常見的泛型模式,推薦的名稱是:
K —— 鍵,比如映射的鍵。
V —— 值,比如 List 和 Set 的內容,或者 Map 中的值。
E —— 異常類。
T —— 泛型。
2. 以上,是將var變量設置為了String類型,當然也可以設置為其他的數據類型,比如Integer等,如果你設置的內容與你制定的泛型類型不一致,則在編譯時將出現錯誤。比如:
- class Point<T>{ // 此處可以隨便寫標識符號,T是type的簡稱
- private T var ; // var的類型由T指定,即:由外部指定
- public T getVar(){ // 返回值的類型由外部決定
- return var ;
- }
- public void setVar(T var){ // 設置的類型也由外部決定
- this.var = var ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Point<Integer> p = new Point<Integer>() ; // 里面的var類型為String類型
- p.setVar("MLDN") ; // 設置字符串
- }
- };
- GenericsDemo.java:13: 錯誤: 無法將類 Point<T>中的方法 setVar應用到給定類型;
- p.setVar("MLDN") ; // 設置字符串
- ^
- 需要: Integer
- 找到: String
- 原因: 無法通過方法調用轉換將實際參數String轉換為Integer
- 其中, T是類型變量:
- T擴展已在類 Point中聲明的Object
- 1 個錯誤
看log就可以發現,我我們已經規定了泛型的類型為Integer,則說明T類型就是Integer,所以在傳入參數時,當然不能傳入String類型的參數了。
3.2 構造方法中使用泛型
構造方法可以為類中的屬性進行初始化,如果類中的屬性用過泛型指定,而又需要通過構造器設置屬性的內容時,那么構造方法的定義與之前並無不同,不需要像聲明類那樣指定泛型。
- class Point<T>{ // 此處可以隨便寫標識符號,T是type的簡稱
- private T var ; // var的類型由T指定,即:由外部指定
- public Point(T var){ // 通過構造方法設置內容
- this.var = var ;
- }
- public T getVar(){ // 返回值的類型由外部決定
- return var ;
- }
- public void setVar(T var){ // 設置的類型也由外部決定
- this.var = var ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Point<String> p = new Point<String>("MLDN") ; // 里面的var類型為String類型
- System.out.println("內容:" + p.getVar()) ;
- }
- };
這里我們講一個泛型的警告問題: 當你為某個類只定了泛型,但是,你實例化該類的對象的時候,並沒有指定泛型的類型,則程序在編譯時會出現警告,警告並不會影響程序的運行。
- class Info<T>{
- private T var ;
- public T getVar(){
- return this.var ;
- }
- public void setVar(T var){
- this.var = var ;
- }
- public String toString(){ // 覆寫Object類中的toString()方法
- return this.var.toString() ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info i = new Info() ; // 警告,沒有指定泛型類型
- i.setVar("MLDN") ; // 設置字符串
- System.out.println("內容:" + i.getVar()) ;
- }
- };
注: GenericsDemo10.java使用了未經檢查或不安全的操作。
注: 有關詳細信息, 請使用 -Xlint:unchecked 重新編譯。
說明: 由於沒有指定泛型類型,則類可以接受任何數據類型,也就是此時的var的類型就是Object,所有的泛型信息都會被擦除。
三、 泛型通配符
3.1 引入泛型通配符
我們先來看一個例子:
- class Info<T>{
- private T var ; // 定義泛型變量
- public void setVar(T var){
- this.var = var ;
- }
- public T getVar(){
- return this.var ;
- }
- public String toString(){ // 直接打印
- return this.var.toString() ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info<String> i = new Info<String>() ; // 使用String為泛型類型
- i.setVar("MLDN") ; // 設置內容
- fun(i) ;
- }
- public static void fun(Info<Object> temp){ // 接收Object泛型類型的Info對象
- System.out.println("內容:" + temp) ;
- }
- };
- GenericsDemo.java:17: 錯誤: 無法將類 GenericsDemo中的方法 fun應用到給定類型;
- fun(i) ;
- ^
- 需要: Info<Object>
- 找到: Info<String>
- 原因: 無法通過方法調用轉換將實際參數Info<String>轉換為Info<Object>
- 1 個錯誤</span>
- public static void main(String args[]){
- Info<String> i = new Info<String>() ; // 使用String為泛型類型
- i.setVar("MLDN") ; // 設置內容
- fun(i) ;
- }
- public static void fun(Info temp){ // 接收Object泛型類型的Info對象
- System.out.println("內容:" + temp) ;
- }
當然,這樣看來程序已經可以正常運行了,但是,我們之前已經指定了泛型,此時卻在方法傳遞過程中把它取消了,總是不妥的,所以,java提供了?通配符來匹配任何的泛型類型。
- public class GenericsDemo{
- public static void main(String args[]){
- Info<String> i = new Info<String>() ; // 使用String為泛型類型
- i.setVar("MLDN") ; // 設置內容
- fun(i) ;
- }
- public static void fun(Info<?> temp){ // 可以接收任意的泛型對象
- System.out.println("內容:" + temp) ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info<?> i = new Info<String>() ; // 使用String為泛型類型
- i.setVar("MLDN") ; // 設置內容
- }
- };
- GenericsDemo.java:16: 錯誤: 無法將類 Info<T>中的方法 setVar應用到給定類型;
- i.setVar("MLDN") ; // 設置內容
- ^
- 需要: CAP#1
- 找到: String
- 原因: 無法通過方法調用轉換將實際參數String轉換為CAP#1
- 其中, T是類型變量:
- T擴展已在類 Info中聲明的Object
- 其中, CAP#1是新類型變量:
- CAP#1從?的捕獲擴展Object
- 1 個錯誤
四、 受限泛型
4.1 泛型上限: 表示參數化的類型可能是所指定類型,或者是其子類。
- class Info<T>{
- private T var ; // 定義泛型變量
- public void setVar(T var){
- this.var = var ;
- }
- public T getVar(){
- return this.var ;
- }
- public String toString(){ // 直接打印
- return this.var.toString() ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info<Integer> i1 = new Info<Integer>() ; // 聲明Integer的泛型對象
- Info<Float> i2 = new Info<Float>() ; // 聲明Float的泛型對象
- i1.setVar(30) ; // 設置整數,自動裝箱
- i2.setVar(30.1f) ; // 設置小數,自動裝箱
- fun(i1) ;
- fun(i2) ;
- }
- public static void fun(Info<? extends Number> temp){ // 只能接收Number及其Number的子類
- System.out.print(temp + "、") ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info<String> i1 = new Info<String>() ; // 聲明String的泛型對象
- i1.setVar("hello") ;
- fun(i1) ;
- }
- public static void fun(Info<? extends Number> temp){ // 只能接收Number及其Number的子類
- System.out.print(temp + "、") ;
- }
- };
fun(i1) ;
^
需要: Info<? extends Number>
找到: Info<String>
原因: 無法通過方法調用轉換將實際參數Info<String>轉換為Info<? extends Number>
1 個錯誤
4.2 泛型下限:使用的泛型只能是本類及其父類類型上應用的時候,就必須使用泛型的下限。
- class Info<T>{
- private T var ; // 定義泛型變量
- public void setVar(T var){
- this.var = var ;
- }
- public T getVar(){
- return this.var ;
- }
- public String toString(){ // 直接打印
- return this.var.toString() ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info<String> i1 = new Info<String>() ; // 聲明String的泛型對象
- Info<Object> i2 = new Info<Object>() ; // 聲明Object的泛型對象
- i1.setVar("hello") ;
- i2.setVar(new Object()) ;
- fun(i1) ;
- fun(i2) ;
- }
- public static void fun(Info<? super String> temp){ // 只能接收String或Object類型的泛型
- System.out.print(temp + "、") ;
- }
- };
五、 泛型與子類繼承
一個類的子類可以通過對象多態性,為其父類實例化,但是在泛型操作中,子類的泛型類型是無法使用父類的泛型類型接受的,例如,Info<String>不能使用 Info<Object>接收。
六、 泛型接口
6.1 定義泛型接口
- interface Info<T>{ // 在接口上定義泛型
- public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
- }
6.2 泛型接口的兩種實現方式
6.2.1 定義子類,在子類的上也使用泛型聲明
- interface Info<T>{ // 在接口上定義泛型
- public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
- }
- class InfoImpl<T> implements Info<T>{ // 定義泛型接口的子類
- private T var ; // 定義屬性
- public InfoImpl(T var){ // 通過構造方法設置屬性內容
- this.setVar(var) ;
- }
- public void setVar(T var){
- this.var = var ;
- }
- public T getVar(){
- return this.var ;
- }
- };
- public class GenericsDemo{
- public static void main(String arsg[]){
- Info<String> i = null; // 聲明接口對象
- i = new InfoImpl<String>("李興華") ; // 通過子類實例化對象
- System.out.println("內容:" + i.getVar()) ;
- }
- };
6.2.2 定義子類,直接指定泛型的具體操作類型
- interface Info<T>{ // 在接口上定義泛型
- public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
- }
- class InfoImpl implements Info<String>{ // 定義泛型接口的子類
- private String var ; // 定義屬性
- public InfoImpl(String var){ // 通過構造方法設置屬性內容
- this.setVar(var) ;
- }
- public void setVar(String var){
- this.var = var ;
- }
- public String getVar(){
- return this.var ;
- }
- };
- public class GenericsDemo{
- public static void main(String arsg[]){
- Info i = null; // 聲明接口對象
- i = new InfoImpl("李興華") ; // 通過子類實例化對象
- System.out.println("內容:" + i.getVar()) ;
- }
- };
七、 泛型方法
7.1 泛型方法中可以定義泛型參數,此時,參數的類型就是傳入數據的類型。
- class Demo{
- public <T> T fun(T t){ // 可以接收任意類型的數據
- return t ; // 直接把參數返回
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Demo d = new Demo() ; // 實例化Demo對象
- String str = d.fun("李興華") ; // 傳遞字符串
- int i = d.fun(30) ; // 傳遞數字,自動裝箱
- System.out.println(str) ; // 輸出內容
- System.out.println(i) ; // 輸出內容
- }
- };
7.2 通過泛型方法,返回泛型類的實例
- class Info<T extends Number>{ // 指定上限,只能是數字類型
- private T var ; // 此類型由外部決定
- public T getVar(){
- return this.var ;
- }
- public void setVar(T var){
- this.var = var ;
- }
- public String toString(){ // 覆寫Object類中的toString()方法
- return this.var.toString() ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info<Integer> i = fun(30) ;
- System.out.println(i.getVar()) ;
- }
- public static <T extends Number> Info<T> fun(T param){
- Info<T> temp = new Info<T>() ; // 根據傳入的數據類型實例化Info
- temp.setVar(param) ; // 將傳遞的內容設置到Info對象的var屬性之中
- return temp ; // 返回實例化對象
- }
- };
7.2 使用泛型,統一傳遞參數的類型
- class Info<T>{ // 指定上限,只能是數字類型
- private T var ; // 此類型由外部決定
- public T getVar(){
- return this.var ;
- }
- public void setVar(T var){
- this.var = var ;
- }
- public String toString(){ // 覆寫Object類中的toString()方法
- return this.var.toString() ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info<String> i1 = new Info<String>() ;
- Info<String> i2 = new Info<String>() ;
- i1.setVar("HELLO") ; // 設置內容
- i2.setVar("李興華") ; // 設置內容
- add(i1,i2) ;
- }
- public static <T> void add(Info<T> i1,Info<T> i2){
- System.out.println(i1.getVar() + " " + i2.getVar()) ;
- }
- };
- public class GenericsDemo{
- public static void main(String args[]){
- Info<Integer> i1 = new Info<Integer>() ;
- Info<String> i2 = new Info<String>() ;
- i1.setVar(30) ; // 設置內容
- i2.setVar("李興華") ; // 設置內容
- add(i1,i2) ;
- }
- public static <T> void add(Info<T> i1,Info<T> i2){
- System.out.println(i1.getVar() + " " + i2.getVar()) ;
- }
- };
編譯時報錯:
- GenericsDemo.java:19: 錯誤: 無法將類 GenericsDemo中的方法 add應用到給定類型;
- add(i1,i2) ;
- ^
- 需要: Info<T>,Info<T>
- 找到: Info<Integer>,Info<String>
- 原因: 不存在類型變量T的實例, 以使參數類型Info<String>與形式參數類型Info<T>一致
- 其中, T是類型變量:
- T擴展已在方法 <T>add(Info<T>,Info<T>)中聲明的Object
- 1 個錯誤
六、 泛型數組
不能創建一個確切泛型類型的數組。如下面代碼會出錯。
List<String>[] lsa = new ArrayList<String>[10];
因為如果可以這樣,那么考慮如下代碼,會導致運行時錯誤。
- List<String>[] lsa = new ArrayList<String>[10]; // 實際上並不允許這樣創建數組
- Object o = lsa;
- Object[] oa = (Object[]) o;
- List<Integer>li = new ArrayList<Integer>();
- li.add(new Integer(3));
- oa[1] = li;// unsound, but passes run time store check
- String s = lsa[1].get(0); //run-time error - ClassCastException
- List<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type
- Object o = lsa;
- Object[] oa = (Object[]) o;
- List<Integer>li = new ArrayList<Integer>();
- li.add(new Integer(3));
- oa[1] = li; //correct
- String s = (String) lsa[1].get(0);// run time error, but cast is explicit
- Integer it = (Integer)lsa[1].get(0); // OK