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