java 泛型generics

參考
為什么使用泛型
JAVA泛型總結(jié)
Java泛型:泛型類、泛型接口和泛型方法
java泛型應(yīng)用實(shí)例 - 自定義泛型類,方法

泛型的本質(zhì):數(shù)據(jù)類型參數(shù)化

為什么使用泛型

我們先看下面的代碼,代碼省略了一些內(nèi)容,但功能是實(shí)現(xiàn)一個棧,這個棧只能處理int數(shù)據(jù)類型:

public class Stack{
        private int[] m_item;
        public int Pop(){...}
        public void Push(int item){...}
        public Stack(int i)
        {
            this.m_item = new int[i];
        }
}

上面代碼運(yùn)行的很好,但是,當(dāng)我們需要一個棧來保存string類型時,該怎么辦呢?很多人都會想到把上面的代碼復(fù)制一份,把int改成string不就行了。當(dāng)然,這樣做本身是沒有任何問題的,但一個優(yōu)秀的程序是不會這樣做的,因?yàn)樗氲饺粢院笤傩枰猯ong、Node類型的棧該怎樣做呢?還要再復(fù)制嗎?優(yōu)秀的程序員會想到用一個通用的數(shù)據(jù)類型object來實(shí)現(xiàn)這個棧:

public class Stack{
        private object[] m_item;
        public object Pop(){...}
        public void Push(object item){...}
        public Stack(int i)
        {
            this.m_item = new[i];
        }
    }

這個棧寫的不錯,他非常靈活,可以接收任何數(shù)據(jù)類型,可以說是一勞永逸。但全面地講,也不是沒有缺陷的,主要表現(xiàn)在:
當(dāng)Stack處理值類型時,會出現(xiàn)裝箱、折箱操作,這將在托管堆上分配和回收大量的變量,若數(shù)據(jù)量大,則性能損失非常嚴(yán)重。
在處理引用類型時,雖然沒有裝箱和折箱操作,但將用到數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)換操作,增加處理器的負(fù)擔(dān)。
在數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)換上還有更嚴(yán)重的問題(假設(shè)stack是Stack的一個實(shí)例):

Node1 x = new Node1();
stack.Push(x);
Node2 y = (Node2)stack.Pop();

上面的代碼在編譯時是完全沒問題的,但由于Push了一個Node1類型的數(shù)據(jù),但在Pop時卻要求轉(zhuǎn)換為Node2類型,這將出現(xiàn)程序運(yùn)行時的類型轉(zhuǎn)換異常,但卻逃離了編譯器的檢查。
泛型用用一個通過的數(shù)據(jù)類型T來代替object,在類實(shí)例化時指定T的類型,運(yùn)行時(Runtime)自動編譯為本地代碼,運(yùn)行效率和代碼質(zhì)量都有很大提高,并且保證數(shù)據(jù)類型安全。

使用泛型

下面是用泛型來重寫上面的棧,用一個通用的數(shù)據(jù)類型T來作為一個占位符,等待在實(shí)例化時用一個實(shí)際的類型來代替。讓我們來看看泛型的威力:

public class Stack<T>
    {
        private T[] m_item;
        public T Pop(){...}
        public void Push(T item){...}
        public Stack(int i)
        {
            this.m_item = new T[i];
        }
}

類的寫法不變,只是引入了通用數(shù)據(jù)類型T就可以適用于任何數(shù)據(jù)類型,并且類型安全的。這個類的調(diào)用方法:

//實(shí)例化只能保存int類型的類
Stack<int> a = new Stack<int>(100);
a.Push(10);
a.Push("8888"); //這一行編譯不通過,因?yàn)轭恆只接收int類型的數(shù)據(jù)
int x = a.Pop();
//實(shí)例化只能保存string類型的類
Stack<string> b = new Stack<string>(100);
b.Push(10);    //這一行編譯不通過,因?yàn)轭恇只接收string類型的數(shù)據(jù)
b.Push("8888");
string y = b.Pop();

這個類和object實(shí)現(xiàn)的類有截然不同的區(qū)別

  • 他是類型安全的。實(shí)例化了int類型的棧,就不能處理string類型的數(shù)據(jù),其他數(shù)據(jù)類型也一樣。
  • 無需裝箱和折箱。這個類在實(shí)例化時,按照所傳入的數(shù)據(jù)類型生成本地代碼,本地代碼數(shù)據(jù)類型已確定,所以無需裝箱和折箱。
  • 無需類型轉(zhuǎn)換。
泛型類型

C#泛型類在編譯時,先生成中間代碼IL,通用類型T只是一個占位符。在實(shí)例化類時,根據(jù)用戶指定的數(shù)據(jù)類型代替T并由即時編譯器(JIT)生成本地代碼,這個本地代碼中已經(jīng)使用了實(shí)際的數(shù)據(jù)類型,等同于用實(shí)際類型寫的類,所以不同的封閉類的本地代碼是不一樣的。按照這個原理,我們可以這樣認(rèn)為:
泛型類的不同的封閉類是分別不同的數(shù)據(jù)類型。
例:Stack<int>和Stack<string>是兩個完全沒有任何關(guān)系的類,你可以把他看成類A和類B,這個解釋對泛型類的靜態(tài)成員的理解有很大幫助。

在JAVA中,對于不同傳入的類型實(shí)參,生成的相應(yīng)對象實(shí)例的類型是不是一樣的呢?

 public class GenericTest {
     public static void main(String[] args) {
         Box<String> name = new Box<String>("corn");
         Box<Integer> age = new Box<Integer>(712);
         System.out.println("name class:" + name.getClass()); 
         System.out.println("age class:" + age.getClass());
         System.out.println(name.getClass() == age.getClass());// true
     }
}

由此,我們發(fā)現(xiàn),在使用泛型類時,雖然傳入了不同的泛型實(shí)參,但并沒有真正意義上生成不同的類型,傳入不同泛型實(shí)參的泛型類在內(nèi)存上只有一個,即還是原來的最基本的類型(本實(shí)例中為Box),當(dāng)然,在邏輯上我們可以理解成多個不同的泛型類型。
究其原因,在于Java中的泛型這一概念提出的目的,導(dǎo)致其只是作用于代碼編譯階段,在編譯過程中,對于正確檢驗(yàn)泛型結(jié)果后,會將泛型的相關(guān)信息擦出,也就是說,成功編譯過后的class文件中是不包含任何泛型信息的。泛型信息不會進(jìn)入到運(yùn)行時階段。
對此總結(jié)成一句話:泛型類型在邏輯上看以看成是多個不同的類型,實(shí)際上都是相同的基本類型。

類型通配符

我們需要一個在邏輯上可以用來表示同時是Box<Integer>和Box<Number>的父類的一個引用類型,由此,類型通配符應(yīng)運(yùn)而生。
類型通配符一般是使用 ? 代替具體的類型實(shí)參。注意了,此處是類型實(shí)參,而不是類型形參!且Box<?>在邏輯上是Box<Integer>、Box<Number>...等所有Box<具體類型實(shí)參>的父類。由此,我們依然可以定義泛型方法,來完成此類需求。

public class GenericTest {
     public static void main(String[] args) { 
         Box<String> name = new Box<String>("corn");
         Box<Integer> age = new Box<Integer>(712);
         Box<Number> number = new Box<Number>(314);
         getData(name);
         getData(age);
         getData(number);
     }
     public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
     }
 }

有時候,我們還可能聽到類型通配符上限和類型通配符下限。具體有是怎么樣的呢?
在上面的例子中,如果需要定義一個功能類似于getData()的方法,但對類型實(shí)參又有進(jìn)一步的限制:只能是Number類及其子類。此時,需要用到類型通配符上限。

 public class GenericTest {
     public static void main(String[] args) {
         Box<String> name = new Box<String>("corn");
         Box<Integer> age = new Box<Integer>(712);
         Box<Number> number = new Box<Number>(314);
         getData(name);
         getData(age);
         getData(number); 
         //getUpperNumberData(name);//1
         getUpperNumberData(age);//2
         getUpperNumberData(number);//3
    }
    public static void getData(Box<?> data) {
         System.out.println("data :" + data.getData());
     }    
     public static void getUpperNumberData(Box<? extends Number> data){
        System.out.println("data :" + data.getData());
     }
 }

此時,顯然,在代碼//1處調(diào)用將出現(xiàn)錯誤提示,而//2 //3處調(diào)用正常。
類型通配符上限通過形如Box<? extends Number>形式定義,相對應(yīng)的,類型通配符下限為Box<? super Number>形式,其含義與類型通配符上限正好相反

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 開發(fā)人員在使用泛型的時候,很容易根據(jù)自己的直覺而犯一些錯誤。比如一個方法如果接收List作為形式參數(shù),那么如果嘗試...
    時待吾閱讀 1,122評論 0 3
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,856評論 25 709
  • 一個朋友問:新手小白,該如何提升。為了解答這個問題,我馬不停蹄去尋找方法。 在混跡不久的自媒體里,把我能找到的大神...
    幸福的原始密碼閱讀 208評論 0 0
  • 和所有女孩一樣,當(dāng)我在步入婚姻殿堂的那一刻,就希望自己能像童話故事里那樣,過著幸福的生活。所以我每天都在想象,為自...
    迷途的小書蟲閱讀 238評論 0 0
  • 1、 Post請求可以通過@Body提交整個表單,也可以通過@Field提交單個字段。2、使用攔截器統(tǒng)一處理Htt...
    ysnows閱讀 332評論 0 0

友情鏈接更多精彩內(nèi)容