1. 程式人生 > >泛型的定義和使用

泛型的定義和使用

一、泛型

1、編譯期確定型別安全——泛型(Generics)

泛型是提供給Javac編譯器使用的。可以限定集合中輸入的型別,讓編譯器在編譯期間避免原始程式的非法輸入,編譯器編譯帶型別說明的集合時會去掉“型別”資訊,使程式執行效率不受影響,對於引數化的泛型型別,getClass()方法的返回值和原始型別完全一樣,由於編譯生成的位元組碼會去掉泛型的型別資訊,只要能跳過編譯器,就可以往某個泛型集合中加入其它型別的資料,例如,用反射得到集合,再呼叫其add方法新增自己不同的物件即可,這樣便可以實現動態內容變化化的陣列。

ArrayList類定義和ArrayList類引用中涉及如下術語:

泛型型別: 整個ArrayList<E>

型別引數(型別變數): E

引數化的型別: 整個ArrayList<Integer>

型別引數的例項: ArrayList中的Integer

typeof: ArrayList<Integer>中的<Integer>

原始型別: ArrayList

2、引數化型別與原始型別的相容性

引數化型別可以引用一個原始型別的物件,編譯報告警告,例如,   

Collection<String> c = new Vector ();

原始型別可以引用一個引數化型別的物件,編譯報告警告,例如,

Collection c = new
Vector<String>();

引數化型別不考慮型別引數的繼承關係:

Vector<String> v = new Vector<Object>()    // 錯誤
Vector<Object> v = new Vector<String>()    // 也錯誤

3、型別擦除

正確理解泛型概念的首要前提是理解型別擦除(type erasure)。 Java中的泛型類似於C++中的模板,但是這種相似性僅限於表面,Java中的泛型基本上都是在編譯器這個層次來實現的。屬於編譯器執行型別檢查和型別診斷,然後生成普通的非泛型的位元組碼,也就是在生成的Java位元組程式碼中是不包含泛型中的型別資訊的,使用泛型的時候加上的型別引數,會被編譯器在編譯的時候去掉。這種實現技術稱為型別擦除。如在程式碼中定義的List<Object>

List<String>等型別,在編譯之後都會變成List。JVM看到的只是List,而由泛型附加的型別資訊對JVM來說是不可見的。Java編譯器會在編譯時儘可能的發現可能出錯的地方,但是仍然無法避免在執行時刻出現型別轉換異常的情況。

很多泛型的奇怪特性都與這個型別擦除的存在有關,包括:

泛型類並沒有自己獨有的Class類物件。比如並不存在List<String>.class或是List<Integer>.class,而只有List.class;

靜態變數是被泛型類的所有例項所共享的。對於宣告為MyClass的類,訪問其中的靜態變數的方法仍然是 MyClass.myStaticVar。不管是通過new MyClass<String>還是new MyClass<Integer>建立的物件,都是共享一個靜態變數。

泛型的型別引數不能用在Java異常處理的catch語句中。因為異常處理是由JVM在執行時刻來進行的。由於型別資訊被擦除,JVM是無法區分兩個異常型別MyException<String>MyException<Integer>的。對於JVM來說,它們都是MyException型別的。也就無法執行與異常對應的catch語句。

4、泛型的定義與使用

我們使用一個泛型首先要定義它,其次就是使用它進行泛型例項化。
一般來說我們有幾種方式去確定泛型類別:

宣告時確定

執行時延後賦值確定

泛型界限確定基本型別

/*
* 泛型界限
*/
static class Apple<T extends InputStream> {
        T data;
}
/*
*正常的泛型
*/  
static class Orange<T>{
        T data;
}

    /**
     * 泛型的例項化 1:聲明確定         2:執行時確定          3:泛型界限 + 執行時確定 
     * 
     * @throws IOException
     */
public static void test3() throws IOException {

        /**
         * 1:聲明確定
         */

        Orange<InputStream> orange = new Orange<>();
        //此處編譯不通過,因為宣告時泛型引數化不會區分繼承關係,屬於精確性的宣告。
        //Orange<InputStream> orange = new Orange<FileInputStream>();

        //此處報錯,因為String並不是InputStream型別
        //orange.data = new String();

        //雖然是建立賦值,但這裡也沒有報錯。此處是由於繼承關係,FileInputStream也是InputStream,而且自動向上轉型為InputStream,但它與泛型界限並不相同,泛型界限限制了T只能是某一個類別的派生,但沒有界限的繼承則可以在宣告的時候修改T至任何一個類別,繼承自這些類別的類也都可以被向上轉型賦值,它的範圍要廣很多。
        orange.data = new FileInputStream(new File("123.txt"));
        System.out.println("1、聲明確定:"+orange.data.getClass().getName());
        System.out.println();


        /**
         * 2:執行時確定
         */
        Orange orange2 = new Orange<>();
        //此處編譯通過,因為沒有在構造的時候初始化泛型型別,因此泛型是由編譯器決定(也就是根據賦值決定)。
        orange2.data = new String();

        System.out.println("2、執行時確定:" + orange2.data.getClass().getName());
        orange2.data = new FileInputStream(new File("123.txt"));
        System.out.println("2、執行時確定:" +orange2.data.getClass().getName());
        System.out.println();

        /**
         * 3:泛型界限 + 執行時確定
         */
        Apple apple = new Apple<>();
        // 報錯,因為String不是繼承自InputStream
        // apple.data = new String();

        // 編譯通過,泛型在編譯期確定型別,因為ObjectInputStream是繼承自InputStream,因此賦值成功
        apple.data = new BufferedInputStream(new FileInputStream("123.txt"));
        System.out.println("3、泛型界限 + 執行時確定:" +apple.data.getClass().getName());
        apple.data = new FileInputStream("123.txt");
        System.out.println("3、泛型界限 + 執行時確定" +apple.data.getClass().getName());

    }

程式輸出結果:

1、聲明確定:java.io.FileInputStream

2、執行時確定:java.lang.String
2、執行時確定:java.io.FileInputStream

3、泛型界限 + 執行時確定:java.io.BufferedInputStream
3、泛型界限 + 執行時確定java.io.FileInputStream
從上面的結果我們可以看出,泛型只是我們躲過編譯器編譯的一個手段,讓我們的程式在執行時擁有更強大的靈活性,可以在編譯期間對泛型,但它也意味著我們的程式很有可能在執行時出現錯誤(轉型失敗引起程式崩潰)。

相關推薦

c#中的自定義類、方法接口

泛型方法 return bsp 其中 tel sts code 方式 void ? 泛型的產生其中一個原因就是為了解決原來集合類中元素的裝箱和拆箱問題: 一、泛型類: /// <summary> /// 返回前臺的消息 /// &

Java方法構造函數

generic 類型參數 -s con ner 實際類型 非靜態方法 gen 尖括號 可以在方法聲明中定義類型參數,它們在方法的返回類型之前的尖括號中指定。包含泛型方法聲明的類型不必是通用類型。可以在非靜態方法聲明中使用為泛型類型指定的類型參數。 示例 以下代碼顯示如何為方

Java的約束限制

實例 == -h 不同 java異常 轉換 component 參數 測試 不能用基本類型實例化類型參數 不能用類型參數代替基本類型:例如,沒有Pair<double>,只有Pair<Double>,其原因是類型擦除。擦除之後,Pair類含有O

集合集合的區別

但是 array 存儲 數據類型的轉換 是的 array對象 編譯期 都沒有 nbsp 他們倆個,一個是弱類型,一個是強類型。 而弱類型是指無法在應用程序編譯期間得到檢查,如array對象,你可以往這個對象中添加數字和字符都沒有問題。但是在遍歷操作的時候可能會牽扯到數據類型

Java中TClass<T>以及Class<?>的理解(轉)

tcl ota 特定 類型 基本 ext pla enum extend 註意:class是java的關鍵字, 在聲明Java類時使用; Class類的實例表示Java應用運行時的類(class ans enum)或接口(interface and annotatio

Android開發之深入理解extendssuper的區別

我想 lis dataset 文檔 cnblogs extend 擦除 選擇 提前 摘要: 什麽是泛型?什麽是擦除邊界?什麽是上界限定或下界限定(子類型限定或超類型限定)?什麽是類型安全?泛型extends關和super關鍵字結合通配符?使用的區別,兩種泛型在實際Andro

在JAVA中返回類使用TObject有什麽區別?

some http cast one gpo aud pre 使用 安全 最近在讀jackson源碼的時候發現有段代碼返回類型寫的是<T> T,而我自己一般寫的是Object。上網搜了下這個語法糖,在stackoverflow上找到一個比較簡單易懂的解釋,搬運過

集合的嵌套(遞歸算法

入學 信息 add getc over import override 很多 tostring 1、  集合的嵌套: 集合的用法其實和數組的用法有很多共同之處,在使用數組的時候,二維數組就是數組的嵌套; 那麽在集合之中是否也可以這樣呢? 當然也是可以的,例如對於最復雜的的m

定義

聲明 logs aid https edt 形式 target -s 參數 泛型的定義:泛型是JDK 1.5的一項新特性,它的本質是參數化類型 ParameterizedType,即帶有類型參數的類型。 類型參數。 https://www.cnblogs.com/baiqi

CLR via C#學習筆記-第十二章-方法其他成員

12.6 泛型方法 方法和類可以各自定義型別引數 定義泛型類、結構或介面時,型別中定義的任何方法都可以引用型別指定的型別引數。 型別引數可以作為方法引數、返回值或方法內部定義的區域性變數的型別使用。 CLR還允許方法指定他自己的型別引數,這些引數也可以作為引數、返回值或區域性變數的型別使用。 在下例

Java程式設計題解與上機指導(第四版)第七章 Java語言的高階特性 7.2藉助定義一個線性表

線性表:由相同型別的物件組成的一個線性結構360百科: 定義 線性表(linear list)是資料結構的一種,一個線性表是n個具有相同特性的資料元素的有限序列。資料元素是一個抽象的符號,其具體含義在不同的情況下一般不同。 在稍複雜的線性表中,一個數據元素可由多個數據項(

Java方法型別萬用字元的區別

泛型方法VS型別萬用字元(兩者可以混用):      1)你會發現所有能用型別萬用字元(?)解決的問題都能用泛型方法解決,並且泛型方法可以解決的更好: 最典型的一個例子就是:          

一句話弄懂Java extends super

之前一直用有道筆記,記錄的東西都是隻要自己看得懂就行了,現在嘗試寫文章,發表一下自己的一點點見解,可能有很多不足之處 廢話不多說,開始正文 <? extends E> 上限萬用字元,用來限制類型的上限 <? super E> 下限萬用字元

[轉]java中?T的區別

在程式碼中經常會看到這樣的函式 public static void printColl(ArrayList<?> al){ Iterator<?> it = al.iterator(); while(it.hasNext())

Java--理解使用 (List<String> list = new ArrayList<String>(); )

List<String> list = new ArrayList<String>(); 第一次看到這行程式碼是一頭霧水,查了好久才弄清楚這是什麼東西,怎麼用,所以記錄下來,方便以後查閱。   首先看一段程式碼 public class Gener

java中的T?的差異性,學習了

java泛型中T和?有什麼區別? T 代表一種型別 加在類上:class SuperClass<A>{} 加在方法上: public <T>void fromArrayToCollection(T[] a, Collection<T>

2.方法

1.定義簡單泛型類: public class Pair<T> { private first; private second; public Pair() { first = null; second = null; } public Pa

使用Generics萬用字元來完成父子類List物件的型別匹配

基本格式 參考:參考文章1 參考文章2     //萬用字元實現 public List<Animal> find(List<? extends Animal> animals, Animal animal){

不能初始化引數陣列

泛型型別在編譯期被擦除,我們在類初始化時將無法獲得泛型的具體引數,比如這樣的程式碼: class Foo<T>{ private T t =new T(); private T[] tArray=new T[5]; private List<T&g

C#基礎:方法

 public class Farm<T> :IEnumerator<T>  where T :Animal//泛型類使用where關鍵字進行約束  {