Java基礎(chǔ)-泛型

Android知識(shí)總結(jié)

一、泛型基礎(chǔ)

1)、什么示泛型
Java泛型是JDK1.5中引入的一個(gè)新特性,泛型提供了編譯時(shí)類(lèi)型安全檢查機(jī)制,該機(jī)制允許程序員在編譯時(shí)檢到非法類(lèi)型。
泛型的本質(zhì)是參數(shù)類(lèi)型,也就是說(shuō)所操作的數(shù)據(jù)類(lèi)型指定為一個(gè)參數(shù)類(lèi)型。
泛型不存在與JNM虛擬機(jī)。
2)、為什么使用泛型

1、泛型可以增強(qiáng)編譯時(shí)錯(cuò)誤檢查,減少因類(lèi)型問(wèn)題引發(fā)的運(yùn)行異常(ClassCastException),因此泛型具有更強(qiáng)的類(lèi)型檢查。

2、泛型可以避免類(lèi)型轉(zhuǎn)換。

    //沒(méi)使用泛型
    private void fun1(){
        List nameList = new ArrayList();
        nameList.add("XaoMing");
        String name = (String) nameList.get(0);
    }
    
    //使用泛型
    private void fun2(){
        List<String> nameList = new ArrayList();
        nameList.add("XaoMing");
        String name = nameList.get(0);
    }

3、泛型可以泛型算法,增加代碼復(fù)用性。

    private <T extends Comparable<T>> int countComparable(T[] array, T t2) {
        int count = 0;
        for (T e : array){
            if (e.compareTo(t2) > 0){
                count ++;
            }
        }
        return count;
    }

3)、Java中的泛型

1、泛型類(lèi)
泛型類(lèi)格式:
class name <T1,T2,...Tn>{}
泛型類(lèi)之影響內(nèi)部的普通方法和變量
2、泛型接口
泛型格式:
interface NameInterface<T> {}
3、泛型方法
定義格式:
private <K,V> boolean compare(Pair<K, V> p1, Pare<K, V>p2){}
調(diào)用格式:
Util.<K, V> compare(p1, p2)

//泛型類(lèi), 泛型接口的使用
class Men1<T, V> : MenInterface<V>{
    var name: T
    var str : V? = null

    constructor(name: T) {
        this.name = name
    }
    
    //泛型方法
    fun <E> getStr1 (str: E) {

    }
    //泛型方法, 其中的T和類(lèi)泛型中的T不是一個(gè)T
    fun <T> getStr2 (str: T) {

    }

    private fun <T : Comparable<T>?> countComparable(array: Array<T>, t2: T): Int {
        var count = 0
        for (e in array) {
            if (e!! > t2) {
                count++
            }
        }
        return count
    }
    override fun getMen(): V {
        return str!!
    }

    override fun setMen(men: V) {
        this.str = men
    }


}

//泛型接口的使用
class Men2 : MenInterface<String>{
    override fun getMen(): String {
        return "XaoMing"
    }

    override fun setMen(men: String) {
        TODO("Not yet implemented")
    }

}

//泛型接口
interface MenInterface<V>{
    fun getMen() : V
    fun setMen(men : V)
}

4)、常見(jiàn)類(lèi)型變量名稱(chēng)

  • E : 表示集合元素類(lèi)型(在Java集合框架中廣泛運(yùn)用)
  • K : 表示關(guān)鍵字類(lèi)型
  • N:表示數(shù)字類(lèi)型
  • V : 表示類(lèi)型
  • T : 表示任意類(lèi)型
  • S,U,V:第二,第三,第四個(gè)類(lèi)型
  • ? :通配符,不能用在類(lèi)中,可應(yīng)用在方法和參數(shù)

5)、原始類(lèi)型
缺少實(shí)際類(lèi)型變量的泛型就是一個(gè)原始類(lèi)型
如:
java

class Box<T>{}
Box box = new Box(); //這個(gè)Box就是Box<T>的原始類(lèi)型

kotlin

class GG<T>
 val gg: GG<*> = GG<Any?>()

二、泛型限制

對(duì)泛型變量的范圍作出限制
1)、單一限制
<T extends X> //表示類(lèi)型的上界,類(lèi)型參數(shù)是X的子類(lèi)
<T super X> //表示類(lèi)型的下界,類(lèi)型參數(shù)是X的超類(lèi)
2)、多種限制
<T extends A & B & C>

  • extends 表達(dá)的意識(shí):這里指的是廣義上的擴(kuò)展,兼有類(lèi)型繼承接口實(shí)現(xiàn)之意。
  • 多種限制下的格式語(yǔ)法要求:如果上限類(lèi)型是一個(gè)類(lèi),必須第一位標(biāo)識(shí)出,否著編譯錯(cuò)誤。且只能由一個(gè)類(lèi),多個(gè)接口
    泛型算法實(shí)現(xiàn)的關(guān)鍵:利用受限類(lèi)型參數(shù)
public class A {}
public interface B {}
public interface C {}
public class D<T extends A & B & C> { }
public static <T extends A & B & C> void setData(T data) { }

kotlin

interface A{
    fun setName()
}
interface B{
    fun setSex()
}
class D<T> where T: A, T: B  {
}

三、PESC原則

//獲取的值是Number,不能設(shè)置值
List<? super Number> list1;
//設(shè)置Number和子類(lèi),獲取對(duì)象是Object
List<? extends Object> list2;

class Foot{}
class Fruit extends Foot {}
class Apple extends Fruit {}
class HongFuShi extends Apple {}
class Orange extends Fruit {}
class GenericType<T>{
    private T date;
    public T getDate() {
        return date;
    }
    public void setDate(T date) {
        this.date = date;
    }
}

class FruitTest{
    public static void print1(GenericType<? extends Fruit> fruit){
        System.out.println(fruit.getDate().toString());
    }
    public static void print2(GenericType<? super Apple> apple){
        System.out.println(apple.getDate().toString());
    }
    public static void use1(){
        print1(new GenericType<Fruit>());  //true
        print1(new GenericType<Apple>()); //true
        print1(new GenericType<HongFuShi>());  //true
        print1(new GenericType<Foot>()); //error 超過(guò)了上線
        
        GenericType<? extends Fruit> genericType = new GenericType<>();
        genericType.setDate(new Apple()); //error 不能設(shè)置數(shù)據(jù)
        Fruit date = genericType.getDate(); //只能訪問(wèn)數(shù)據(jù)
    }

    public static void use2(){
        print2(new GenericType<Apple>()); //true
        print2(new GenericType<Fruit>());  //true
        print2(new GenericType<HongFuShi>()); //error 超過(guò)線下
        print2(new GenericType<Orange>()); //error 不是同一個(gè)類(lèi)型
        
        //因?yàn)?Apple和下限HongFuShi可以安全轉(zhuǎn)型為Apple, Fruit不能安全轉(zhuǎn)型
        GenericType<? super Apple> genericType = new GenericType<>();
        genericType.setDate(new Apple()); //true
        genericType.setDate(new HongFuShi()); //true
        genericType.setDate(new Fruit()); //error super 設(shè)置數(shù)據(jù),只能設(shè)置自己和下限
        Object date = genericType.getDate(); //獲取的數(shù)據(jù)是Object類(lèi)型
    }
}

四、泛型擦除

功能:保證了泛型不在運(yùn)行時(shí)出現(xiàn)
類(lèi)型消除應(yīng)用場(chǎng)合

  • 編譯器會(huì)把泛型類(lèi)型中所有的類(lèi)型參數(shù)替換成他們的上(下)限,如果沒(méi)有對(duì)應(yīng)類(lèi)型作出限制,那么就會(huì)替換成Object類(lèi)型。因此,編譯出的字節(jié)碼僅僅包含常規(guī)類(lèi),接口和方法。
  • 多種限制<T extends A & B & C>擦除后用A。
  • 在必要的時(shí)候插入類(lèi)型轉(zhuǎn)換以保證類(lèi)型安全。
  • 生成橋方法以在擴(kuò)展泛型時(shí)保持多態(tài)性。
    Bridge Methods 橋方法
  • 當(dāng)編譯一個(gè)擴(kuò)展參數(shù)化類(lèi)的類(lèi),或一個(gè)實(shí)現(xiàn)參數(shù)化類(lèi)型接口的接口時(shí),編譯器有可能會(huì)創(chuàng)建一個(gè)合成方法,名為橋方法。它是類(lèi)型擦除過(guò)程的一部分。
  • java 的泛型偽泛型,JVM中不支持泛型,為了兼容低版本(JDK1.5以下)

五、編譯

  • 1)、用javac把java文件編譯成class文件
    javac DD.java


  • 2)、用javap反編譯class文件字節(jié)碼
    javap -c DD.class


六、知識(shí)點(diǎn)

1、) ArrayList<String> 、ArrayList<Object>和ArrayList<?>是否可以相互轉(zhuǎn)化
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
ArrayList<?> list3 = new ArrayList<>();
//不能同一類(lèi)型,雖然String是Object的子類(lèi),但是ArrayList<String> 、ArrayList<Object>不是同一類(lèi)型
list2 = list1;
//可以直接轉(zhuǎn)換,因?yàn)??是通配?list3 = list1;

但是泛型類(lèi)可以繼承或者擴(kuò)展其他泛型類(lèi),比如List和ArrayList


2、) 限制
class D<T> {
    private T data;
   
    public D() {
         //不能實(shí)例化類(lèi)型變量
        this.data = new T(); //error
    }
    
    //泛型類(lèi)的靜態(tài)上下文中類(lèi)型變量失效, 所以靜態(tài)域或方法里不能引用類(lèi)型變量
    //引文不知道泛型類(lèi)型,泛型類(lèi)型是做類(lèi)初始化時(shí)候才知道的
    private static T instance1(){} //error

    //靜態(tài)方法是放行方法可以
    private static <T>T instance2(T data){  //true
        return data;
    }
    //error
    private <V extends Exception> void doWork1(V v){
        try {
            
        } catch (T e){  //error 不能捕獲泛型類(lèi)的實(shí)例
            
        }
    }
    //true 泛型可以拋出異常 
    private <V extends Exception> void doWork2(V v) throws V{
        try {

        } catch (Exception e){
            throw v;
        }
    }
    public static void main(String[] argc){
        //不能用基本類(lèi)型實(shí)例化類(lèi)型參數(shù)
        D<double> d1; //error
        //包裝類(lèi)型可以作為泛型
        D<Double> d2 = new D<>(); //true

        //運(yùn)行時(shí)類(lèi)型查詢(xún)只適用于原始類(lèi)型
        //泛型不能用 instanceof
        if (d1 instanceof D<Double>){} //error
         if (d1 instanceof D<T>){} //error

        D<String> d3 = new D<>(); 
        //因?yàn)榉盒筒脸?,獲取泛型的原生類(lèi)型進(jìn)行比較
        System.out.println(d2.getClass() == d3.getClass()); //true

        //不能創(chuàng)建參數(shù)化類(lèi)型的數(shù)組
        //可以定義泛型數(shù)組,但不能new一個(gè)泛型數(shù)組
        D<String>[] d4; //true
        D<String>[] d5 = new D<String>[10]; //error
    }
}

//泛型不能繼承異常
class A<T> extends Exception { //error
}
    //無(wú)法創(chuàng)建類(lèi)型化實(shí)例
    private static <E> void append(List<E> list) throws Exception{
        E elem = new E(); //compile-time error
        list.add(elem);
    }
    
    //通過(guò)反射創(chuàng)建一個(gè)參數(shù)化類(lèi)型實(shí)例
    private static <E> void append(List<E> list, Class<E> clazz) throws Exception{
        E e = clazz.newInstance();
        list.add(e);
    }

七、虛擬機(jī)是如何實(shí)現(xiàn)泛型的

  • 泛型思想早在C++語(yǔ)言的模板(Template)中就開(kāi)始生根發(fā)芽,在Java語(yǔ)言處于還沒(méi)有出現(xiàn)泛型的版本時(shí),只能通過(guò)Object是所有類(lèi)型的父類(lèi)和類(lèi)型強(qiáng)制轉(zhuǎn)換兩個(gè)特點(diǎn)的配合來(lái)實(shí)現(xiàn)類(lèi)型泛化。,由于Java語(yǔ)言里面所有的類(lèi)型都繼承于java.lang.Object,所以O(shè)bject轉(zhuǎn)型成任何對(duì)象都是有可能的。但是也因?yàn)橛袩o(wú)限的可能性,就只有程序員和運(yùn)行期的虛擬機(jī)才知道這個(gè)Object到底是個(gè)什么類(lèi)型的對(duì)象。在編譯期間,編譯器無(wú)法檢查這個(gè)Object的強(qiáng)制轉(zhuǎn)型是否成功,如果僅僅依賴(lài)程序員去保障這項(xiàng)操作的正確性,許多ClassCastException的風(fēng)險(xiǎn)就會(huì)轉(zhuǎn)嫁到程序運(yùn)行期之中。
  • 泛型技術(shù)在C#和Java之中的使用方式看似相同,但實(shí)現(xiàn)上卻有著根本性的分歧,C#里面泛型無(wú)論在程序源碼中、編譯后的IL中(Intermediate Language,中間語(yǔ)言,這時(shí)候泛型是一個(gè)占位符),或是運(yùn)行期的CLR中,都是切實(shí)存在的,List<int>與List<String>就是兩個(gè)不同的類(lèi)型,它們?cè)谙到y(tǒng)運(yùn)行期生成,有自己的虛方法表和類(lèi)型數(shù)據(jù),這種實(shí)現(xiàn)稱(chēng)為類(lèi)型膨脹,基于這種方法實(shí)現(xiàn)的泛型稱(chēng)為真實(shí)泛型。
  • Java語(yǔ)言中的泛型則不一樣,它只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來(lái)的原生類(lèi)型(Raw Type,也稱(chēng)為裸類(lèi)型)了,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此,對(duì)于運(yùn)行期的Java語(yǔ)言來(lái)說(shuō),ArrayList<int>與ArrayList<String>就是同一個(gè)類(lèi),所以泛型技術(shù)實(shí)際上是Java語(yǔ)言的一顆語(yǔ)法糖,Java語(yǔ)言中的泛型實(shí)現(xiàn)方法稱(chēng)為類(lèi)型擦除,基于這種方法實(shí)現(xiàn)的泛型稱(chēng)為偽泛型。
  • 將一段Java代碼編譯成Class文件,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后,將會(huì)發(fā)現(xiàn)泛型都不見(jiàn)了,程序又變回了Java泛型出現(xiàn)之前的寫(xiě)法,泛型類(lèi)型都變回了原生類(lèi)型
    public static String method(List<String> str){
        return "ok";
    }
    public static Integer method(List<Integer> str){
        return 1;
    }
  • 開(kāi)發(fā)工具編譯器:檢測(cè)是否為同一個(gè)類(lèi)類(lèi)型,檢測(cè)方法名和參數(shù)是否相同。
  • JDK編譯器:檢測(cè)是否為同一個(gè)類(lèi)類(lèi)型,檢測(cè)方法名、返回類(lèi)型和參數(shù)是否相同。

解析:在開(kāi)發(fā)工具編譯器中是同一個(gè)類(lèi)型,因?yàn)閰?shù)List<T>擦除后是Object對(duì)象,所以參數(shù)相同。JDK編譯器 中不是同種方法,因?yàn)榉祷仡?lèi)型不同。

  • 上面這段代碼是不能被編譯的,因?yàn)閰?shù)List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類(lèi)型List<E>,擦除動(dòng)作導(dǎo)致這兩種方法的特征簽名變得一模一樣。
  • 由于Java泛型的引入,各種場(chǎng)景(虛擬機(jī)解析、反射等)下的方法調(diào)用都可能對(duì)原有的基礎(chǔ)產(chǎn)生影響和新的需求,如在泛型類(lèi)中如何獲取傳入的參數(shù)化類(lèi)型等。因此,JCP組織對(duì)虛擬機(jī)規(guī)范做出了相應(yīng)的修改,引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來(lái)的參數(shù)類(lèi)型的識(shí)別問(wèn)題,Signature是其中最重要的一項(xiàng)屬性,它的作用就是存儲(chǔ)一個(gè)方法在字節(jié)碼層面的特征簽名,這個(gè)屬性中保存的參數(shù)類(lèi)型并不是原生類(lèi)型,而是包括了參數(shù)化類(lèi)型的信息。修改后的虛擬機(jī)規(guī)范要求所有能識(shí)別49.0以上版本的Class文件的虛擬機(jī)都要能正確地識(shí)別Signature參數(shù)。
  • 另外,從Signature屬性的出現(xiàn)我們還可以得出結(jié)論,擦除法所謂的擦除,僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦除,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息,這也是我們能通過(guò)反射手段取得參數(shù)化類(lèi)型的根本依據(jù)。

八、泛型擦除后恢復(fù)實(shí)例

因?yàn)樵诰幾g成CLASS是,在JVM有個(gè)Signature 會(huì)對(duì)泛型弱記憶;然后可以Type類(lèi)中設(shè)置泛型類(lèi)型,從而找到Signature 中記憶的泛型類(lèi)型。

1)、導(dǎo)入google 的 gson 原理

 implementation 'com.google.code.gson:gson:2.6.2'

2)、代碼

        Response<Date> dateResponse = new Response<>("200", "true", new Date("XaoHua"));
        String str = gson.toJson(dateResponse);
        System.out.println(str);

        /**
        * 用自定義的 TypeRefrence 代替 google 的 TypeToken
         * 有花括號(hào){}:代表匿名內(nèi)部類(lèi),創(chuàng)建一個(gè)匿名內(nèi)部?jī)?nèi)實(shí)力對(duì)向
         * 無(wú)花括號(hào){}:創(chuàng)建實(shí)例對(duì)象
         */
        Response<Date> date = gson.fromJson(str, new TypeRefrence<Response<Date>>(){}.getType());
        System.out.println(date.toString());

3)、自定義gson的TypeToken類(lèi)型

    /**
     * 當(dāng)構(gòu)造方法為protected, 或者  abstract class 創(chuàng)建時(shí)必須帶花括號(hào){}
     *
     * @param <T>
     */
    abstract class TypeRefrence<T> {
        Type type;

        protected TypeRefrence() {
            //獲得泛型類(lèi)型
            Type genericSuperclass = getClass().getGenericSuperclass();
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            //因?yàn)榉盒皖?lèi)型可以定以多個(gè)A<T, v ...>所以是數(shù)組
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            type = actualTypeArguments[0];
        }

        public Type getType() {
            return type;
        }
    }

4)、type類(lèi)型子接口

  • TypeVariable:泛型類(lèi)型變量,可以泛型上下限等信息
  • ParameterizedType:具體的泛型類(lèi)型,可以獲取元數(shù)據(jù)中泛型簽名類(lèi)型(泛型真實(shí)類(lèi)型)。
  • GenericArrayType:當(dāng)需要描述的泛型是泛型類(lèi)數(shù)組時(shí),比如List[],Map[],此接口會(huì)作為T(mén)ype的實(shí)現(xiàn)。
  • WildcardType:通配符泛型,獲取上下限信息。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 在泛型出現(xiàn)以前,類(lèi)和方法只能接受具體的類(lèi)型。假設(shè)我們自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的ArrayList,用來(lái)持有類(lèi)A的實(shí)例,它可...
    辛美爾閱讀 3,120評(píng)論 0 5
  • Java工程師知識(shí)樹(shù)[http://m.itdecent.cn/p/db77d19a25f6] / Ja...
    HughJin閱讀 755評(píng)論 0 1
  • 泛型概述以及泛型類(lèi) 泛型就是類(lèi)型參數(shù)化,處理的數(shù)據(jù)類(lèi)型不是固定的,而是可以作為參數(shù)傳入; 泛型的核心: 告訴編譯器...
    皮多堡閱讀 751評(píng)論 0 0
  • 1、為什么引入泛型 bug是編程的一部分,我們只能盡自己最大的能力減少出現(xiàn)bug的幾率,但是誰(shuí)也不能保證自己寫(xiě)出的...
    冰河winner閱讀 610評(píng)論 0 1
  • 簡(jiǎn)介 說(shuō)起各種高級(jí)語(yǔ)言,不得不談泛型,當(dāng)我們?cè)谑褂胘ava集合的時(shí)候,會(huì)發(fā)現(xiàn)集合有個(gè)缺點(diǎn):把一個(gè)對(duì)象“丟進(jìn)”集合之...
    雙木ll之林閱讀 293評(píng)論 0 1

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