Java泛型原理到實(shí)戰(zhàn)一文通

一. 泛型的本質(zhì)是什么:

  1. 是寫(xiě)給編譯器使用的“語(yǔ)法糖”
        List<String> list=new ArrayList<String>(); //<String>會(huì)被編譯器看到愕掏,但會(huì)在字節(jié)碼中刪除
        list.add("abc");//編譯成功:編譯器對(duì)“abc” instaceof String檢查,符合通過(guò)
//        list.add(100); //編譯失敹ド :編譯器對(duì)100 instaceof String檢查饵撑,不符合失敗
        String s=list.get(0); //編程器將代碼轉(zhuǎn)換為:String s=(String)list.get(0); 再進(jìn)行編譯

而JDK6+的編譯器將更加智能,第一行代碼可以自動(dòng)推測(cè)出對(duì)象實(shí)際類型的“泛型類型”

List<String> list=new ArrayList<>();
  1. 以下運(yùn)行結(jié)果為真:
        ArrayList<Number> list1=new ArrayList<>();
        ArrayList<Object> list2=new ArrayList<>();
        boolean b=list1.getClass()==list2.getClass();//rs: true

沒(méi)有新的Class唆貌,所有的泛型信息滑潘,都會(huì)在編譯后進(jìn)行“型別擦除”。

  1. 結(jié)論:
    (1) 泛型是在源代碼中加入的語(yǔ)法锨咙,為編譯器提供:(1)編譯檢查(2)源代碼生成语卤,這兩大工作。
    (2) 擴(kuò)展認(rèn)知:“注解技術(shù)(Annotation)”實(shí)質(zhì)上是對(duì)泛型技術(shù)的擴(kuò)展,通過(guò)我們對(duì)源代碼的“修飾”(加入@xxx)粹舵,從而讓編譯器(進(jìn)一步:可以是執(zhí)行器)獲取更多的“糖”钮孵,從而可以做到:語(yǔ)法檢查(@Override)、源代碼生成(@Getter)眼滤、運(yùn)行特殊生命周期(@Autowared)等一系列的工作巴席,而以上這些工作,只需要在源代碼中修改诅需,而不在需要使用繁瑣的XML漾唉,從而使用Java以一種全新的姿態(tài)展現(xiàn)給開(kāi)發(fā)者,即:后Java時(shí)代诱担。
    (3) 回想下:junit,lombok,spring,mybatis,JPA,spirng-cloud等毡证,都是在這種大技術(shù)背景下的產(chǎn)物。

二. 泛型的作用:

1. 可以讓編譯器提前檢查 一些運(yùn)行時(shí)異常:

List<String> list=new ArrayList<String>();
 list.add(100);//編譯失斈柘伞:

2. 讓“數(shù)據(jù)類型參數(shù)化”:

(1)如下代碼完成了一個(gè)通用的“結(jié)果類型”設(shè)計(jì):

public class CommonResult<T> {
    private int code;
    private String message;
    //可以認(rèn)為:T 是一種可變的數(shù)據(jù)類型(數(shù)據(jù)類型參數(shù)化)
    private T entity;
    public T getEntity(){
        return entity;
    }
    public void setEntity(T entity){
        this.entity=entity;
    }
}

如此設(shè)計(jì):CommonResult將不會(huì)加為entity的數(shù)據(jù)類型有多種可能料睛,而去設(shè)計(jì)出相應(yīng)的CommonResult。
(2)結(jié)論:變量名稱的參數(shù)化摇邦,讓“方法的調(diào)用”與“方法的實(shí)現(xiàn)”在【變量名稱】達(dá)到解藕的目的恤煞;變量類型的參數(shù)化,讓“方法的調(diào)用”與“方法的實(shí)現(xiàn)”在【變量類型】達(dá)到解藕的目的施籍。
(3)聯(lián)想:java認(rèn)為“數(shù)據(jù)類型”是“算法+數(shù)據(jù)結(jié)構(gòu)”的結(jié)合體居扒,但FaaS時(shí)代的迫切任務(wù)是:把“算法”獨(dú)立成為一種數(shù)據(jù)類型,“Java的Lamda”就是為此而誕生(以前是使用接口完成丑慎,但太笨重了O参埂!竿裂!玉吁,此論點(diǎn)與泛型關(guān)系不大,但需要注意會(huì)有Lamda類型參數(shù)的存在腻异,同樣可以結(jié)合泛型进副,達(dá)到更靈活的目的)

三、泛型語(yǔ)法施加的主體有哪些:

1. 泛型類:

以下形式都是泛型類悔常,T影斑、E、V代表著可變的數(shù)據(jù)類型机打,class中任何出現(xiàn)變量類型的地方都可以使用(域矫户,形參類型,返回類型姐帚、局部變量類型吏垮、異常類型), 以下是一個(gè)相對(duì)極端的例子:

public class CommonResult<T,E,V extends Throwable> {
    private T entity;
    private E e;
    private V v;
    public CommonResult(T entity,E e,V v){
        this.entity=entity;
        this.e=e;
        this.v=v;
    }
    public E ProcessEntity(T entity) throws V{
        this.entity=entity;
        if(Math.random()>0.5) {
            throw v;
        }
        return e;
    }
...

要引起注意的是: T,E,V代表著未知的類型障涯,所以在代碼中不可以做任何有假設(shè)其類型的行為:如new(假設(shè)它是一個(gè)非抽象類),instanceof(假設(shè)它是某一類型)等操作,這也是使用泛型的副作用膳汪,沒(méi)有銀彈唯蝶。

2. 泛型接口:

public interface WorkService<T> {
    T produce();
    void consume(T t);
}

3. 泛型方法:

    public <M> M process(M m){
        return m;
    }

以上代碼理解:
(1) 此方法可以位于泛型或非泛型類中
(2)<M> 的作用有兩個(gè):一是聲明此方法是泛型方法;二是此方法中可以使用M做為參數(shù)化類型的符號(hào)遗嗽。
(3)常見(jiàn)的錯(cuò)誤:認(rèn)為在泛型類粘我,使用了帶有參數(shù)化類型的方法,就是泛型方法:

public interface WorkService<T> {
    T produce();//這是不是泛型方法痹换,只是使用了參數(shù)化類型符號(hào)的方法
}

四征字、所謂的“形參”和“實(shí)參”問(wèn)題:

在定義類、接口娇豫、方法時(shí)匙姜,相當(dāng)于定義了數(shù)據(jù)類型的“形參”,傳入方法總結(jié)如下:

1. 【聲明變量類型】時(shí)傳入實(shí)參:

List<String> list;
WorkService<Number> ws;

此時(shí)冯痢,泛型類或接口中所有的包含T(或其它)的變量聲明將被Strring或Number替換氮昧,語(yǔ)法檢查機(jī)制和代碼生成機(jī)制將隨之變化。
需要注意浦楣,并不需要在實(shí)例化時(shí)指定袖肥,目前的編譯器可以智能的填入。

2. 【實(shí)現(xiàn)或繼承時(shí)】傳入實(shí)參:

public interface WorkService<T> {
    T produce();
    void consume(T t);
}

//=========實(shí)現(xiàn)或繼承式傳入"類型實(shí)參"==============//
class TomWrok implements WorkService<String>{
    @Override
    public String produce() {
        return null;
    }
    @Override
    public void consume(String t) {

    }
}

注意幾點(diǎn):
(1)此時(shí)振劳,所有的替換工作必須由硬編碼方式完成椎组,
(2)TomWork類并不是泛型類,當(dāng)然也可以根據(jù)需要將之變成泛型類历恐,但此時(shí)和它的泛型接口r 的泛型類型已經(jīng)沒(méi)有任何關(guān)系了寸癌,因?yàn)橐呀?jīng)確定為String類型了。

/**============================================
 * 實(shí)現(xiàn)或繼承式傳入"類型實(shí)參"
 * 繼承將Tomwork變?yōu)榉盒皖惾踉簦琓只是符號(hào)灵份,可以是任何字母
 * TomWrok確定了接口中實(shí)參,但又引入了新的形參
 ============================================**/

class TomWrok<T> implements WorkService<String>{
    private T t;
    @Override
    public String produce() {
        return null;
    }
    @Override
    public void consume(String t) {

    }
}

(3)以這種方式哮洽,只是將形參進(jìn)行了傳遞,并沒(méi)有實(shí)現(xiàn)弦聂,子類仍為泛型類

class Work<T> implements WorkService<T>{
    @Override
    public T produce() {
        return null;
    }

    @Override
    public void consume(T t) {

    }
}

3. 【方法調(diào)用時(shí)】傳入:

此方式只是針對(duì)“泛型方法”:

 public static <M> M process(M m) {
        return m;
    }

    public static void main(String[] args) {
        String s = "abc";
        String rs = process(s);//由傳入實(shí)參的聲明類型做為實(shí)參
        Number m = new Integer(123);
        Number rm = process(m);//由傳入實(shí)參的聲明類型做為實(shí)參
        int i = process(123);//由傳入實(shí)參的實(shí)際類型推斷做為實(shí)參
    }

四鸟辅、對(duì)“參數(shù)化類型”的模糊限定:

1. 原理:

(1) 我們的需要是:全方面的“IS 關(guān)系”的檢查,但是目前編譯器做不到]汉7肆埂!

Java編譯器捺檬,可以對(duì)變量類型進(jìn)行"IS關(guān)系"進(jìn)行檢查再层,但無(wú)法對(duì)”泛型類型“的變量中的參數(shù)化類型進(jìn)行”IS關(guān)系檢查“。即:List<Number> :只能對(duì)ArrayList IS List的檢查,但是不能做Integer IS Number的檢查聂受。
上述問(wèn)題:就是本節(jié)要討論的解決方法蒿秦,我們要進(jìn)行【準(zhǔn)全面檢查】

原因是:List<String>和List<Integer>‘是同一個(gè)List class類型,這也稱為類型擦除蛋济,這樣編譯器在進(jìn)行”參數(shù)化類型的檢查時(shí)“棍鳖,就無(wú)法獲取有效的信息;但實(shí)際上編譯器可以做的更加高級(jí)碗旅,從源代碼中獲取類型渡处,再進(jìn)行遞歸方式的檢查,但這樣就會(huì)把編譯器做的足夠的復(fù)雜祟辟,導(dǎo)致編譯速度過(guò)慢医瘫,從而導(dǎo)致基于編譯器的工具鏈不具備生產(chǎn)價(jià)值。

Java在運(yùn)行時(shí)無(wú)法對(duì)傳入“由泛型類”生成的對(duì)象旧困,進(jìn)行“泛型類型”檢查醇份。即:List<String>和List<Integer>‘是同一個(gè)List class類型(這稱為類型擦除)。
觀察如下案例:

    /*
     * @param list:此時(shí)只能通過(guò)基于 "IS List"的檢查叮喳,
     * 而不能通過(guò)"IS LIST && IS Number"的檢查
     */
    public static void fn1(List<Number> list){

    }
    public static void main(String[] args) {

        ArrayList<Number> list1=new ArrayList<>();
        /**
         * 此時(shí)編譯器有足夠的"類型信息"被芳,對(duì)"IS List"進(jìn)行檢查,此時(shí)編譯檢查通過(guò)
         */
        fn1(list1);


        ArrayList<Integer> list2=new ArrayList<>();
        /**
         * 此時(shí)編譯器有足夠的"類型信息"馍悟,對(duì)"IS List"進(jìn)行檢查,
         * 但沒(méi)有足夠的信息畔濒,對(duì)"IS Number"進(jìn)行檢查(因?yàn)樾蛣e擦除,同時(shí)現(xiàn)階段編譯器沒(méi)有能力讀到Number后進(jìn)行檢查)
         * 基于安全角度锣咒,此時(shí)編譯不通過(guò)(信息不足侵状,任可錯(cuò)殺)
         */

        //fn1(list2);
    }
2. 通配符閃亮登場(chǎng):

泛型體系中設(shè)計(jì)了“?extends”毅整,原理是對(duì)“參數(shù)化類型”進(jìn)地適當(dāng)?shù)南薅ㄈば郑瑥亩咕幾g器適當(dāng)?shù)慕槿耄_(dá)到在編譯期讀取足夠多的信息悼嫉,完成“準(zhǔn)全方面的”的“IS”檢查艇潭。
觀察如下案例:

    public static void fn2(List<? extends Number> list){

    }
    public static void main(String[] args) {

        ArrayList<Integer> list2=new ArrayList<>();
        /**
         * 此時(shí)編譯器有足夠的"類型信息",對(duì)"IS List"進(jìn)行檢查,
         * 也有了足夠的信息戏蔑,對(duì)"IS Number"進(jìn)行檢查(不是從類信息查詢蹋凝,而是從源代碼中讀到了 extends Number后進(jìn)行檢查)
         * 此時(shí):ArrayList IS List && Integer IS Number,編譯通過(guò)
         */
        fn2(list2);

        ArrayList<String> list3=new ArrayList<>();
        //此時(shí):ArrayList IS List 但是 String IS Not Number,編譯不通過(guò)
//        fn2(list3);
...
  }

泛型體系又進(jìn)而衍生出了一種更加“高級(jí)”的方式:“? super ”,完成了“參數(shù)化類型”的向上檢查总棵。
觀察以下案例:


    public static void fn3(List<? super Number> list){

    }
    public static void main(String[] args) {

        ArrayList<Integer> list4=new ArrayList<>();
        //list4 is List;但是 Integer不是Number的祖先鳍寂,編譯不通過(guò)
        //fn3(list4);

        ArrayList<Object> list5=new ArrayList<>();
        //list5 is List && Object 是 Number的祖先,編譯通過(guò)
        fn3(list5);
        ...
  }

五情龄、使用通配符的副作用(建議把前面的熟悉之后再看):

沒(méi)有銀彈迄汛,在使用通配符時(shí)捍壤,我們會(huì)根據(jù)對(duì)參數(shù)的不同操作采用不同的?與 extends鞍爱、super的組合鹃觉,一般原則稱為:PECS(Producer Extends Customer Super),案例如下:

1. Producer Extends :

    public static void fn2(List<? extends Number> list){
        /**
         * 進(jìn)行消費(fèi)類型的操作(傳入對(duì)象)硬霍,是不安全的
         * 由于使用extends帜慢,代表著"參數(shù)化類型"有上限,而無(wú)下限唯卖,
         * 此時(shí)可以使用ArrayList<BigDecimal> 類型的實(shí)參粱玲,從而導(dǎo)致運(yùn)行時(shí)的異常,編譯器為預(yù)期的錯(cuò)誤而禁止
         */
//        list.add(100); //針對(duì)任何類型的參數(shù)拜轨,消費(fèi)類型操作都無(wú)法進(jìn)行抽减,這是希望看到的結(jié)果

        /**
         * 進(jìn)行生產(chǎn)類型的操作(產(chǎn)生該類型的對(duì)象),將是大體上安全的
         * 如果獲取的引用聲明是Number或父類型橄碾,是絕安全的
         * 如果是Number的子類型,需要做向下強(qiáng)轉(zhuǎn)卵沉,但不一定安全
         */
        Number m1=list.get(0);
        Integer m2=(Integer) list.get(0);
        Object o1=list.get(0);//多態(tài)引用:Object ref到Number,沒(méi)有問(wèn)題

        /**
         * String并不在Number的繼承樹(shù)之下,這會(huì)在編譯期中被檢查出來(lái)法牲,而無(wú)法通過(guò)
         * 這也是使用extends的原動(dòng)力:即:Producer use Extends==PE
         */

        //String s1=(String)list.get(0); 編譯無(wú)法通過(guò)史汗,是希望看到的結(jié)果

    }


    public static void main(String[] args) {

        ArrayList<BigDecimal> list1=new ArrayList<>();
        fn2(list1);//此時(shí)fn2中如果使用list.add(100),將發(fā)生錯(cuò)誤,所以編譯器不允許通過(guò)
        ...
  }

2. Producer use Extends

    public static void fn3(List<? super Number> list){
        //傳入的list可以是List<Number的祖先們>,所以無(wú)法直接由編譯生成強(qiáng)轉(zhuǎn)的代碼拒垃,只能硬編碼
//        Number number= list.get(0);
        Number number=(Number)list.get(0);//硬編碼強(qiáng)轉(zhuǎn)停撞,提醒我們不應(yīng)該在Producer中使用 super

        /**
         * 此時(shí)可以確認(rèn)"參數(shù)化類型的下限是Number",此時(shí)傳入fn3的list只能是:"IS LIST && ?是Number的祖先",
         * 形如:ArrayList<Object> 這樣的類型悼瓮,
         * 此時(shí)如下的操作都是安全的戈毒,
         */
        list.add(100);
        list.add(100.0);
        Number n=new Float(2.4);
        list.add(n);
        //Number的父類型,只能強(qiáng)轉(zhuǎn)横堡,提醒我們安全的消費(fèi)類型是Number
        list.add((Number) new Object());
        /**
         * 由于String 并不是Number的祖先埋市,所以編譯器直接檢出錯(cuò)誤,
         * 即: Consumer use Super
         */
//        list.add("abc"); 這是我們希望看到的結(jié)果

    }


    public static void main(String[] args) {

        ArrayList<Number> list1=new ArrayList<>();
        fn3(list1);

        ArrayList<Object> list2=new ArrayList<>();
        fn3(list2);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市道宅,隨后出現(xiàn)的幾起案子胸蛛,更是在濱河造成了極大的恐慌,老刑警劉巖胚泌,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異肃弟,居然都是意外死亡零蓉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)敌蜂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人津肛,你說(shuō)我怎么就攤上這事章喉。” “怎么了秸脱?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵部蛇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我巷查,道長(zhǎng)抹腿,這世上最難降的妖魔是什么岛请? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任崇败,我火速辦了婚禮房蝉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搭幻。我一直安慰自己,他們只是感情好松申,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布贸桶。 她就那樣靜靜地躺著桌肴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坠七。 梳的紋絲不亂的頭發(fā)上旗笔,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天拄踪,我揣著相機(jī)與錄音,去河邊找鬼撮弧。 笑死姚糊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舌厨。 我是一名探鬼主播忿薇,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼揉燃!你這毒婦竟也來(lái)了筋栋?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抢腐,失蹤者是張志新(化名)和其女友劉穎襟交,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體捣域,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焕梅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斜棚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脂新,死狀恐怖粗梭,靈堂內(nèi)的尸體忽然破棺而出级零,到底是詐尸還是另有隱情,我是刑警寧澤奏纪,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布序调,位于F島的核電站,受9級(jí)特大地震影響发绢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜经柴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一墩朦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧牛哺,春花似錦劳吠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至便瑟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脊框,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工沉御, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昭灵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓试疙,卻偏偏與公主長(zhǎng)得像抠蚣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嘶窄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 參考:java核心技術(shù) 一羊初、Java泛型的實(shí)現(xiàn)方法:類型擦除 前面已經(jīng)說(shuō)了,Java的泛型是偽泛型晦攒。為什么說(shuō)Jav...
    御前灬碼夫閱讀 1,069評(píng)論 0 7
  • 問(wèn):請(qǐng)說(shuō)說(shuō)下面代碼片段中注釋行執(zhí)行結(jié)果和原因得哆? 答:上面代碼段注釋行執(zhí)行情況解釋如下。三個(gè) add 方法都是非法的...
    Little丶Jerry閱讀 1,003評(píng)論 0 1
  • 問(wèn):請(qǐng)比較深入的談?wù)勀銓?duì) Java 泛型擦除的理解和帶來(lái)的問(wèn)題認(rèn)識(shí)栋操? 答:Java 的泛型是偽泛型饱亮,因?yàn)樵诰幾g期間...
    Little丶Jerry閱讀 1,724評(píng)論 2 1
  • 表情是什么近上,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒剔宪。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了感帅,難過(guò)就哭了地淀。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,027評(píng)論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者,不喜歡去冒險(xiǎn)她倘,但是人生放棄了冒險(xiǎn)作箍,也就放棄了無(wú)數(shù)的可能。 ...
    yichen大刀閱讀 6,050評(píng)論 0 4