摘要:泛型方法泛型類中可以定義靜態(tài)非靜態(tài)的泛型方法。上述泛型類會被替換成下面形式一般使用第一個(gè)限定類型替換變?yōu)樵碱愋停瑳]有限定類型,使用替換。
引言
在面向?qū)ο蟮氖澜缋铮覀內(nèi)绻枰粋€(gè)容器來盛裝對象。舉個(gè)例子:一個(gè)籃子。我們可以用這個(gè)籃子裝蘋果,也可以用這個(gè)籃子裝香蕉。基于 OOP 的思想,我們不希望為蘋果和香蕉分別創(chuàng)建不同的籃子;同時(shí),我們希望放進(jìn)籃子里的是蘋果,拿出來的還是蘋果。于是,Java 程序員提出了「泛型」的概念——一種類似于 C++ 模板的技術(shù)。
早期程序員使用如下代碼創(chuàng)建一個(gè)泛型集合:
public class ArrayList{ private Object[] elementData; ... public Object get(int i); public void add(Object o); }
我們可以看出,對與這個(gè)集合而言,取出 (get) 和放入時(shí)都沒有進(jìn)行類型檢查。因此,如果我們不記得放入的順序,把取出的對象進(jìn)項(xiàng)強(qiáng)制類型轉(zhuǎn)換,很可能出現(xiàn) ClassCastException。因此,真正的泛型是可以在編譯時(shí)期,對數(shù)據(jù)類型進(jìn)行檢查,保證安全。如下面代碼所示:
ArrayListlist = new ArrayList<>();
P.S. <>里的String叫做類型參數(shù)。
使用泛型,為我們提供了如下優(yōu)點(diǎn):
更強(qiáng)大的編譯時(shí)期的類型檢查
避免不必要的類型轉(zhuǎn)換,如:
Listlist = new ArrayList<>(3); String str = list.get(0);
讓程序能夠?qū)崿F(xiàn)通用的算法
泛型類泛型中使用名為泛型參數(shù)表明可以傳入類或方法的對象類型,這種設(shè)計(jì)實(shí)現(xiàn)了類型參數(shù)化(可以把同一類型的類作為參數(shù)進(jìn)行傳遞),如下面的代碼所示:
泛型類示例
public class Pair{ private T first; private T last; public Pair(){} public Pair(T first, T last){ this.first = frist; this.last = last; } public T getFirst(); public T getLast(); }
泛型方法示例
public class Util{ // 簡單的泛型方法 public staticT getMiddle(T...a){ return a[a.length/2]; } // 帶限定符的泛型方法,如果有多個(gè)限定符,使用 & 連接多個(gè)接口或超類 public static T min(T...a){ // 具體實(shí)現(xiàn) } }
注意,這里的泛型參數(shù)(type parameter)在上述示例中指的是用大寫字母 T 表示的值,而泛型實(shí)參(type argument)則是指 T a 中的 a。根據(jù)慣例,泛型參數(shù)通常如下命名:
E:表示一個(gè)元素,Java 集合框架中使用最多
K:鍵
N:數(shù)字
T:類型
V:值
S,U,V:其它類型
原始類型(raw type)原始類型指的是,不包括泛型參數(shù)的類型,如上述泛型類中的 Pair。我們可以通過原生類型構(gòu)造對象:
Pair pair = new Pair();
同時(shí),可以通過泛型參數(shù)構(gòu)造對象:
Pairpair = new Pair<>();
但是,如果把一個(gè)通過原生類型獲取的對象指向一個(gè)通過泛型參數(shù)生成的參數(shù)會報(bào) unchecked warning,如下面的代碼:
Pair pair = new Pair(); Pair繼承和子類型pair1 = pair;
在 Java 中,有繼承的概念,簡而言之,就是一個(gè)類型可以指向它的兼容類型,如:
Object object = new Object(); Integer integer = new Integer(20); object = integer;
上述代碼表示:Integer IS-A Object。這種概念在泛型中也適用。如下定義:
public class Box{ public void add(T t); }
那么一個(gè) Box 的對象可以增加任意 Number 子類的值。但是 Box
泛型類中可以定義靜態(tài)、非靜態(tài)的泛型方法。泛型方法的語法為:<泛型參數(shù)類型列表> + 返回類型 + 泛型參數(shù)列表。
靜態(tài)方法
public staticvoid foo(T t){ }
非靜態(tài)方法
public void foo(T t){ }類型限定
在某種情況下,我們希望方法只接受特定類型的參數(shù),可以使用如下語法實(shí)現(xiàn):
public void inspect(U u){ // 這里是邏輯處理 }
上述代碼中,該泛型方法只接受為 Number 類型的參數(shù)。同樣,也可以在泛型類上加以限制:
public class Utils{ // 這里的 T 必須為 Number 類型 private T t; }
當(dāng)然,也可以使用多重限制,如下面代碼所示:
public class Utils{ }
P.S. 限制中的類必須放在接口的前面。
類型推斷類型推斷是:編譯器去推斷調(diào)用方法的參數(shù)的類型的能力。
如,泛型方法中:
public void addBox(Box box){ // 這里是處理代碼 }
不必通過 obj.addBox(box) 調(diào)用, 可以省略。
構(gòu)造方法中:
// 類型推斷 Map> map = new HashMap<>();
其中,構(gòu)造方法中的泛型還可以這樣用:
// 定義泛型類 public class Box通配符{ public Box(T t){ } } // 實(shí)例化一個(gè)對象 public class Application{ void method(){ Box box = new Box<>("); } }
通配符 ? 表示一個(gè)未知的類型,可用于參數(shù)的類型、字段以及局部變量中,但不可用于調(diào)用泛型方法里的類型參數(shù)、泛型對象實(shí)例化以及泛型超類里。
// 可以 public void foo(Pair extends Number> pair){ // 可以 Pair super Integer> foo; } // 可以 private Pair super Integer> pair;上界通配符
上界通配符表明需要最高限定的類型,下面的代碼用來計(jì)算所有類型為數(shù)字的集合的總和:
public double sumList(List extends Number>){ // 這里做邏輯處理 }無界限通配符
使用無界限通配符表示不確定的類型,以下兩種情況可以使用無界限通配符:
當(dāng)方法的參數(shù)可以用 Object 對象替換
方法的實(shí)現(xiàn)不依賴具體的類型
比如,有一個(gè)打印集合對象的方法:
// 定義一個(gè)打印集合對象列表的方法 public void printList(List> list){ for(Object obj: list){ // 打印list } } // 調(diào)用方法 Listintegers = Arrays.asList(1,2,3); List strings = Arrays.asList("A","B","C"); printList(integers); printList(strings);
P.S. List> 和 List 不同,List> 只能插入 null 而 List 可以插入任何對象。
下界通配符使用下界統(tǒng)配符,表明最低限度的類型,如:
public double sumList(List super Duble>){ // 這里做邏輯處理 }通配符和子類型
在本文的繼承和子類里,提到過:Box
可以看出,泛型中的 extends 的確限定了上界(父類);super 的確限定了下界(子類型);? 是所有泛型的超類(類似 Object)。
泛型的繼承關(guān)系(父子類型關(guān)系)可以通過下面的韋恩圖解釋:
我們不妨用某一泛型所占的面積表示其層次關(guān)系,面積大的在繼承關(guān)系上層次高。由上圖很容易看出: super Integer> 的繼承層次比 super Number> 的繼承層次高;相應(yīng)地, extends Integer> 的繼承層次比 extends Number> 的繼承層次低。
使用泛型的場景調(diào)用一個(gè)方法:foo(src, dest); 把 src 看做入?yún)ⅲ?b>dest 看做出參,基于以下規(guī)則決定是否使用和如何使用泛型:
入?yún)⑹褂蒙辖缤ㄅ浞?b>extends
出參使用下界通配符:super
入?yún)⒖梢杂?Object 代替的,使用無邊通配符
需要獲取入?yún)⒑统鰠⒌淖兞浚灰褂猛ㄅ浞?/p>
這種原則也叫做 PECS(Producer Extends Consumer Super) 原則。
類型擦除類型擦除確保被參數(shù)化的類型不會創(chuàng)建新的類,不會產(chǎn)生運(yùn)行時(shí)的開銷。
泛型擦除時(shí),編譯器做了一點(diǎn)小小的工作:如果該泛型參數(shù)有邊界限制,替換成它的邊界;否則,用 Object 替換。
上述泛型類 Pair
class Pair{ Object first; Object last; public Object getFirst(){} public Object getLast(){} }
P.S. 一般使用第一個(gè)限定類型替換變?yōu)樵碱愋停瑳]有限定類型,使用 Object 替換。
橋接方法當(dāng)子類繼承(或?qū)崿F(xiàn))父類(或接口)的泛型方法時(shí),在子類中指明了具體的類型。編譯器會自動構(gòu)建橋接方法(bridge method)。如:
class Node{ private T t; public Node(T t){ setT(t); } public void setT(T t){ this.t = t; } } class MyNode extends Node { public MyNode(Integer i){ super(i); } public void setT(Integer i){ super.setT(i); } }
在上述代碼中,編譯時(shí)期,由于泛型擦除,Node 中的方法為 setT(Object t) 而 MyNode 中的方法為 setT(Inetger i) 。簽名不匹配,不再是重寫,因此,編譯器為 MyNode 生成如下橋接方法:
// 橋接方法 public void setT(Object i){ setData((Integer)i); } public void setT(Integer i){ super.setData(i); }非具體化類型 非具體化類型定義
具體類型(Reifiable Type)指的是:原始數(shù)據(jù)類型、非泛型類型、原生類型和調(diào)用不受限的通配等在運(yùn)行時(shí)期,信息不會丟失的類型。
非具體類型(Non-Reifiable Type)在運(yùn)行時(shí)期不能獲取其所有的信息,如 JVM 無法區(qū)別 List
堆污染指的是:一個(gè)參數(shù)化類型指向一個(gè)非該參數(shù)化類型對象的過程。通常是,在程序中進(jìn)行了一些操作,使編譯時(shí)期發(fā)生未檢查(unchecked)警告時(shí)發(fā)生。如:混用原始類型(Raw Type)和參數(shù)化類型。
使用非具體化類型做可變參數(shù)的潛在缺陷當(dāng)使用可變參數(shù)作為泛型輸入?yún)?shù)時(shí),會造成堆污染。如:
可以通過如下注解消除編譯時(shí)期的警告:
@SafeVarargs
@SuppressWarnings({"unchecked", "varargs"})
泛型的限制雖然泛型是如此的便利,但不免有缺點(diǎn):
不能用基本類型實(shí)例化類型參數(shù)
// 編譯出錯(cuò) Listarray = new ArrayList<>();
不能通過類型參數(shù)實(shí)例化對象
public staticvoid foo(List list){ // 編譯出錯(cuò) E element = new E(); list.add(element); }
不能創(chuàng)建泛型變量類型的靜態(tài)字段
public class Foo{ // 編譯出錯(cuò) private static T field; }
不能使用 instanceof 來確認(rèn)參數(shù)類型
public staticvoid foo(List list){ // 編譯出錯(cuò) if(list instanceof ArrayList ){ } }
不能創(chuàng)建參數(shù)化類型數(shù)組
// 編譯出錯(cuò) List[] strings = new ArrayList<>[2];
不能拋出或捕獲泛型類實(shí)例
// 編譯出錯(cuò) public class FooExceptionextends Exception{ }
不能重載擦除后有同樣方法簽名的方法
public class Example{ // 編譯出錯(cuò) public void print(Setstring){ } public void print(Set integer){ } }
運(yùn)行時(shí)類型查詢只適用于原始類型
Varargs 警告
泛型類的靜態(tài)上下文的類型變量無效
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/71063.html
摘要:本人生性愚鈍,在大學(xué)期間沒能好好領(lǐng)略等面向?qū)ο缶幊痰镊攘Α,F(xiàn)借助一些較為權(quán)威的書籍資料,將基礎(chǔ)知識里比較重要的東西整理成文,命名從基礎(chǔ)學(xué)。如果博文不慎侵犯了您的著作權(quán),請聯(lián)系我。 和很多大學(xué)一樣,我的學(xué)校也是從 Java 、C++ 入手,教給我們面向?qū)ο?(OOP) 的思想。本人生性愚鈍,在大學(xué)期間沒能好好領(lǐng)略 Java 等面向?qū)ο缶幊痰镊攘Α,F(xiàn)借助一些較為權(quán)威的書籍資料,將 Java...
摘要:作為技術(shù)書籍或者視頻,講解一門語言的時(shí)候都是從最底層開始講解,底層的基礎(chǔ)有哪些呢首先是整個(gè),讓我們對這門語言先混個(gè)臉熟,知道程序的基本結(jié)構(gòu),順帶著還會說一下注釋是什么樣子。 2018年新年剛過,就迷茫了,Java學(xué)不下去了,不知道從哪里學(xué)了。 那么多細(xì)節(jié)的東西,我根本記不住,看完就忘。 剛開始學(xué)習(xí)的時(shí)候熱情萬丈,持續(xù)不了幾天就慢慢退去。 作為技術(shù)書籍或者視頻,講解一門語言的時(shí)候都是...
摘要:進(jìn)階多線程開發(fā)關(guān)鍵技術(shù)后端掘金原創(chuàng)文章,轉(zhuǎn)載請務(wù)必將下面這段話置于文章開頭處保留超鏈接。關(guān)于中間件入門教程后端掘金前言中間件 Java 開發(fā)人員最常犯的 10 個(gè)錯(cuò)誤 - 后端 - 掘金一 、把數(shù)組轉(zhuǎn)成ArrayList 為了將數(shù)組轉(zhuǎn)換為ArrayList,開發(fā)者經(jīng)常... Java 9 中的 9 個(gè)新特性 - 后端 - 掘金Java 8 發(fā)布三年多之后,即將快到2017年7月下一個(gè)版...
閱讀 2225·2021-11-22 13:52
閱讀 3862·2021-11-10 11:36
閱讀 1406·2021-09-24 09:47
閱讀 1092·2019-08-29 13:54
閱讀 3365·2019-08-29 13:46
閱讀 1946·2019-08-29 12:16
閱讀 2113·2019-08-26 13:26
閱讀 3475·2019-08-23 17:10