<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java 泛型詳解

          共 7830字,需瀏覽 16分鐘

           ·

          2020-11-25 17:22

          點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

          ? 作者?|??低吟不作語

          來源 |? urlify.cn/uiEfye

          66套java從入門到精通實戰(zhàn)課程分享

          1、概述

          在 Java5 以前,普通的類和方法只能使用特定的類型:基本數(shù)據(jù)類型或類類型,如果編寫的代碼需要應用于多種類型,這種嚴苛的限制對代碼的束縛就會很大

          Java5 的一個重大變化就是引入泛型,泛型實現(xiàn)了參數(shù)化類型,使得你編寫的組件(通常是集合)可以適用于多種類型。泛型的初衷是通過解耦類或方法與所使用的類型之間的約束,使得類或方法具備最寬泛的表達力。然而很快你就會發(fā)現(xiàn),Java 中的泛型并沒有你想的那么完美,甚至存在一些令人迷惑的實現(xiàn)


          2、泛型類

          促成泛型出現(xiàn)的最主要動機之一就是為了創(chuàng)建集合類,集合用于存放要使用到的對象。現(xiàn)有一個只能持有單個對象的類:

          class?Automobile?{}

          public?class?Holder1?{
          ????private?Automobile?a;
          ????public?Holder1(Automobile?a)?{?this.a?=?a;?}
          ????Automobile?get()?{?return?a;?}
          }

          如果沒有泛型,那么就必須明確指定其持有的對象的類型,會導致該復用性不高,它無法持有其他類型的對象,我們當然不希望為每個類型都編寫一個新類

          在 Java5 以前,為了解決這個問題,我們可以讓這個類直接持有 Object 類型的對象,這樣就可以持有多種不同類型的對象了。但通常而言,我們只會用集合存儲同一類型的對象。泛型的主要目的之一就是用來約定集合要存儲什么類型的對象,并且通過編譯器確保規(guī)約得以滿足

          所以,與其使用 Object,我們更希望先指定一個類型占位符,稍后再決定具體使用什么類型。由此我們需要使用類型參數(shù),用尖括號括住,放在類名后面。然后在使用這個類時,再用實際的類型替換此類型參數(shù)

          public?class?GenericHolder?{
          ????private?T?a;
          ????public?GenericHolder()?{}
          ????public?void?set(T?a)?{?this.a?=?a;?}
          ????public?T?get()?{?return?a;?}

          ????public?static?void?main(String[]?args)?{
          ????????//?在?Java7?中右邊的尖括號可以為空
          ????????GenericHolder?h2?=?new?GenericHolder();
          ????????GenericHolder?h3?=?new?GenericHolder<>();
          ????????h3.set(new?Automobile());?//?此處有類型校驗
          ????????Automobile?a?=?h3.get();??//?無需類型轉(zhuǎn)換
          ????????//-?h3.set("Not?an?Automobile");?//?報錯
          ????}
          }


          3、元組類庫


          有時一個方法需要能返回多個對象,而 return語句只能返回單個對象,解決的方法就是創(chuàng)建一個對象,用它來打包想要返回的多個對象。元組的概念正是基于此,元組將一組對象直接打包存儲于單一對象中,可以從該對象讀取其中元素,卻不允許向其中存儲新對象(這個概念也稱數(shù)據(jù)傳輸對象或信使)

          元組可以具有任意長度,元組中的對象可以是不同類型的,我們希望能為每個對象指明類型,這時泛型就派上用場了。例如下面是一個可以存儲兩個對象的元組:

          public?class?Tuple?{
          ????public?final?A?a1;
          ????public?final?B?a2;
          ????public?Tuple(A?a,?B?b)?{?a1?=?a;?a2?=?b;?}
          ????public?String?rep()?{?return?a1?+?",?"?+?a2;?}

          ????@Override
          ????public?String?toString()?{
          ????????return?"("?+?rep()?+?")";
          ????}
          }

          使用 final 修飾成員變量可以保證其不被修改,如果用戶想存儲不同的元素,那么就必須創(chuàng)建新的 Tuple 對象。當然也可以允許用戶重新對 a1、a2 賦值,但無疑前一種形式會更加安全

          利用繼承機制可以實現(xiàn)長度更長的元組:

          public?class?Tuple3?extends?Tuple2?{
          ????public?final?C?a3;
          ????public?Tuple3(A?a,?B?b,?C?c)?{
          ????????super(a,?b);
          ????????a3?=?c;
          ????}

          ????@Override
          ????public?String?rep()?{
          ????????return?super.rep()?+?",?"?+?a3;
          ????}
          }


          4、泛型方法


          到目前為止,我們已經(jīng)研究了參數(shù)化整個類,其實還可以參數(shù)化類中的方法。類本身是否是泛型,與它的方法是否是泛型并沒有什么直接關(guān)系。我們應該盡可能使用泛型方法,通常將單個方法泛型化要比將整個類泛型化要更加清晰易懂

          要定義泛型方法,請將泛型參數(shù)列表放置在返回值之前:

          public?class?GenericMethods?{
          ????public??void?f(T?x)?{
          ????????System.out.println(x.getClass().getName());
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????GenericMethods?gm?=?new?GenericMethods();
          ????????gm.f("");
          ????????gm.f(1);
          ????????gm.f(1.0);
          ????????gm.f(1.0F);
          ????????gm.f('c');
          ????????gm.f(gm);
          ????}
          }

          使用泛型方法時,通常不需要指定參數(shù)類型,因為編譯器會找出這些類型,這稱為類型參數(shù)推斷,因此,對 f() 的調(diào)用看起來像普通的方法調(diào)用,而且像是被重載了無數(shù)次一樣

          5、泛型擦除

          當你開始深入研究泛型時,你會發(fā)現(xiàn)一個殘酷的現(xiàn)實:在泛型代碼內(nèi)部,無法獲取任何有關(guān)泛型參數(shù)類型的信息

          class?Frob?{}
          class?Fnorkle?{}
          class?Quark?{}
          class?Particle?{}

          public?class?LostInformation?{

          ????public?static?void?main(String[]?args)?{
          ????????List?list?=?new?ArrayList<>();
          ????????Map?map?=?new?HashMap<>();
          ????????Quark?quark?=?new?Quark<>();
          ????????Particle?p?=?new?Particle<>();
          ????????System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
          ????????System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
          ????????System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
          ????????System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
          ????}
          }

          /*?Output:
          [E]
          [K,V]
          [Q]
          [POSITION,MOMENTUM]
          */

          正如上例中輸出所示,你只能看到用作參數(shù)占位符的標識符,這并非有用的信息。Java 泛型是使用擦除實現(xiàn)的,這意味著當你在使用泛型時,任何具體的類型信息都被擦除了,你唯一知道的就是你在使用一個對象。因此 List 和 List 在運行時實際上是相同的類型,它們都被擦除成原生類型 List

          再來看一個例子:

          class?Manipulator?{
          ????private?T?obj;

          ????Manipulator(T?x)?{
          ????????obj?=?x;
          ????}

          ????//?Error:?cannot?find?symbol:?method?f():
          ????public?void?manipulate()?{
          ????????obj.f();
          ????}
          }

          public?class?Manipulation?{
          ????public?static?void?main(String[]?args)?{
          ????????HasF?hf?=?new?HasF();
          ????????Manipulator?manipulator?=?new?Manipulator<>(hf);
          ????????manipulator.manipulate();
          ????}
          }

          因為擦除,Java 編譯器無法將 manipulate() 方法能調(diào)用 obj 的 f() 方法這一需求映射到 HasF 具有 f() 方法這個事實上。為了調(diào)用 f(),我們必須協(xié)助泛型類,為泛型類給定一個邊界,以此告訴編譯器只能接受遵循這個邊界的類型。這里重用了 extends 關(guān)鍵字。由于有了邊界,下面的代碼就能通過編譯:

          public?class?Manipulator2?{
          ????private?T?obj;

          ????Manipulator2(T?x)?{
          ????????obj?=?x;
          ????}

          ????public?void?manipulate()?{
          ????????obj.f();
          ????}
          }

          邊界 聲明 T 必須是 HasF 類型或其子類。如果情況確實如此,就可以安全地在 obj 上調(diào)用 f() 方法。泛型類型參數(shù)會擦除到它的第一個邊界(可能有多個邊界,稍后你將看到)。我們還提到了類型參數(shù)的擦除。編譯器實際上會把類型參數(shù)替換為它的擦除,就像上面的示例,T 擦除到了 HasF,就像在類的聲明中用 HasF 替換了 T 一樣。如果我們愿意,完全可以把上例的 T 替換成 HashF,效果也是一樣的,那么泛型的意義又何在呢?

          這提出了很重要的一點:泛型只有在類型參數(shù)比某個具體類型(以及其子類)更加“泛化”,代碼能跨多個類工作時才有用。因此,使用類型參數(shù)通常比簡單的聲明類更加復雜。但是,不能因此認為使用 形式就是有缺陷的。你必須查看所有的代碼,從而確定代碼是否復雜到必須使用泛型的程度

          有關(guān)泛型擦除的困惑,其實是 Java 為實現(xiàn)泛型的一種妥協(xié),因為泛型并不是 Java 語言出現(xiàn)時就有的。擦除減少了泛型的泛化性,泛型類型只有在靜態(tài)類型檢測期間才出現(xiàn),在此之后,程序中的所有泛型類型都將被擦除,替換為它們的非泛型上界。例如, List 這樣的類型注解會被擦除為 List,普通的類型變量在未指定邊界的情況下會被擦除為 Object

          在 Java5 以前編寫的類庫是沒有使用泛型的,而作者可能打算重新用泛型編寫,或者根本不打算這樣做。Java 設(shè)計者們既要保證舊代碼和類文件依然合法,還得考慮當某個類庫變?yōu)榉盒蜁r,不會破壞依賴于它的代碼和應用。Java 設(shè)計者們最終認為泛型是唯一可行的解決方案,擦除使得向泛型的遷移成為可能,為了實現(xiàn)非泛型的代碼和泛型代碼共存,必須將某個類庫使用了泛型這樣的“證據(jù)”擦除

          基于上述觀點,當你在編寫泛型代碼時,必須時刻提醒自己,你只是看起來擁有有關(guān)參數(shù)的類型信息而言。因為擦除,我們無法在運行時知道確切的類型,為了補償擦除帶來的弊端,我們可以為所需的類型顯示傳遞一個 Class 對象,以在類型表達式中使用它

          class?Building?{
          }

          class?House?extends?Building?{
          }

          public?class?ClassTypeCapture?{
          ????Class?kind;

          ????public?ClassTypeCapture(Class?kind)?{
          ????????this.kind?=?kind;
          ????}

          ????public?boolean?f(Object?arg)?{
          ????????return?kind.isInstance(arg);
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????ClassTypeCapture?ctt1?=
          ????????????????new?ClassTypeCapture<>(Building.class);
          ????????System.out.println(ctt1.f(new?Building()));
          ????????System.out.println(ctt1.f(new?House()));
          ????????ClassTypeCapture?ctt2?=
          ????????????????new?ClassTypeCapture<>(House.class);
          ????????System.out.println(ctt2.f(new?Building()));
          ????????System.out.println(ctt2.f(new?House()));
          ????}
          }

          6、邊界和通配符

          由于擦除會刪除類型信息,因此唯一可用于無限制泛型參數(shù)的方法是那些 Object 可用的方法。邊界允許我們對泛型使用的參數(shù)類型施以類型,將參數(shù)限制為某類型的子集,那么就可以調(diào)用該子集中的方法。為了應用約束,Java 泛型使用了 extends 關(guān)鍵字

          class?Coord?{
          ????public?int?x,?y,?z;
          }

          interface?Weight?{
          ????int?weight();
          }

          class?Solid?{
          ????T?item;

          ????Solid(T?item)?{
          ????????this.item?=?item;
          ????}

          ????T?getItem()?{
          ????????return?item;
          ????}

          ????int?getX()?{
          ????????return?item.x;
          ????}

          ????int?getY()?{
          ????????return?item.y;
          ????}

          ????int?getZ()?{
          ????????return?item.z;
          ????}

          ????int?weight()?{
          ????????return?item.weight();
          ????}
          }

          class?Bounded
          ????????extends?Coord?implements?Weight?{

          ????@Override
          ????public?int?weight()?{
          ????????return?0;
          ????}
          }

          public?class?BasicBounds?{
          ????public?static?void?main(String[]?args)?{
          ????????Solid?solid?=
          ????????????????new?Solid<>(new?Bounded());
          ????????solid.getY();
          ????????solid.weight();
          ????}
          }

          引入通配符可以在泛型實例化時更加靈活地控制,也可以在方法中控制方法的參數(shù),具體語法如下:

          • ? extends T:表示 T 或 T 的子類

          • ? super T:表示 T 或 T 的父類

          • ?:表示可以是任意類型


          7、值得注意的問題

          在這里主要闡述在使用 Java 泛型時會出現(xiàn)的各類問題

          1. 任何基本數(shù)據(jù)類型不能作為類型參數(shù)

          Java 泛型的限制之一是不能將基本類型用作類型參數(shù)。因此,不能創(chuàng)建 ArrayList 之類的東西。解決方法是使用基本類型的包裝器類以及自動裝箱機制。如果創(chuàng)建一個 ArrayList,并將基本類型 int 應用于這個集合,那么你將發(fā)現(xiàn)自動裝箱機制將自動地實現(xiàn) int 到 Integer 的雙向轉(zhuǎn)換,這幾乎就像是有一個 ArrayList 一樣

          2. 實現(xiàn)參數(shù)化接口

          一個類不能實現(xiàn)同一個泛型接口的兩種變體,由于擦除的原因,這兩個變體會成為相同的接口。下面是產(chǎn)生這種沖突的情況:

          interface?Payable?{}

          class?Employee?implements?Payable?{}

          class?Hourly?extends?Employee?implements?Payable?{}

          Hourly 不能編譯,因為擦除會將 Payable 和 Payable 簡化為相同的類 Payable,這樣,上面的代碼就意味著在重復兩次地實現(xiàn)相同的接口。十分有趣的是,如果從 Payable 的兩種用法中都移除掉泛型參數(shù)(就像編譯器在擦除階段所做的那樣)這段代碼就可以編譯

          3. 轉(zhuǎn)型和警告

          使用帶有泛型類型參數(shù)的轉(zhuǎn)型不會有任何效果,例如:

          class?Storage?{
          ????
          ????private?Object?obj;

          ????Storage()?{
          ????????obj?=?new?Object();
          ????}

          ????@SuppressWarnings("unchecked")
          ????public?T?pop()?{
          ????????return?(T)obj;
          ????}
          }

          public?class?GenericCast?{

          ????public?static?void?main(String[]?args)?{
          ????????Storage?storage?=?new?Storage<>();
          ????????System.out.println(storage.pop());
          ????}
          }

          如果沒有 @SuppressWarnings 注解,編譯器將對 pop() 產(chǎn)生 “unchecked cast” 警告。由于擦除的原因,編譯器無法知道這個轉(zhuǎn)型是否是安全的,并且 pop() 方法實際上并沒有執(zhí)行任何轉(zhuǎn)型。這是因為,T 被擦除到它的第一個邊界,默認情況下是 Object,因此 pop() 實際上只是將 Object 轉(zhuǎn)型為 Object

          4. 重載

          下面的程序是不能編譯的,因為擦除,所以重載方法產(chǎn)生了相同的類型簽名

          public?class?UseList?{
          ????void?f(List?v)?{}
          ????void?f(List?v)?{}
          }




          粉絲福利:實戰(zhàn)springboot+CAS單點登錄系統(tǒng)視頻教程免費領(lǐng)取

          ???

          ?長按上方微信二維碼?2 秒
          即可獲取資料



          感謝點贊支持下哈?

          瀏覽 34
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  北条麻美在线无码 | 一区二区操逼豆花口骚逼 | 成人中文字幕免费最近 | 久久婷婷国产综合精品_国产激情 | 婷婷激情视频在线播放 |