摘要:虛擬機中并沒有泛型類型對象,所有的對象都是普通類。其原因就是泛型的擦除。中數組是協變的,泛型是不可變的。在不指定泛型的情況下,泛型變量的類型為該方法中的幾種類型的同一個父類的最小級,直到。
引入泛型的主要目標有以下幾點:
類型安全
泛型的主要目標是提高 Java 程序的類型安全
編譯時期就可以檢查出因 Java 類型不正確導致的 ClassCastException 異常
符合越早出錯代價越小原則
消除強制類型轉換
泛型的一個附帶好處是,使用時直接得到目標類型,消除許多強制類型轉換
所得即所需,這使得代碼更加可讀,并且減少了出錯機會
潛在的性能收益
由于泛型的實現方式,支持泛型(幾乎)不需要 JVM 或類文件更改
所有工作都在編譯器中完成
編譯器生成的代碼跟不使用泛型(和強制類型轉換)時所寫的代碼幾乎一致,只是更能確保類型安全而已
使用方式泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。
類型參數的意義是告訴編譯器這個集合中要存放實例的類型,從而在添加其他類型時做出提示,在編譯時就為類型安全做了保證。
參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口、泛型方法。
通配符有時候希望傳入的類型有一個指定的范圍,從而可以進行一些特定的操作,這時候就是通配符邊界登場的時候了。
泛型中有三種通配符形式:
> 無限制通配符
extends E> extends關鍵字聲明了類型的上界,表示參數化的類型可能是所指定的類型,或者是此類型的子類
super E> super關鍵字聲明了類型的下界,表示參數化的類型可能是指定的類型,或者是此類型的父類
無限制通配符 >要使用泛型,但是不確定或者不關心實際要操作的類型,可以使用無限制通配符(尖括號里一個問號,即 > ),表示可以持有任何類型。
大部分情況下,這種限制是好的,但這使得一些理應正確的基本操作都無法完成。
private void swap(List> list, int i, int j){ Object o = list.get(i); list.set(j,o); }
這個代碼看上去應該是正確的,但 Java 編譯器會提示編譯錯誤,set 語句是非法的。編譯器提示我們把方法中的 List> 改成List
? 和 Object 不一樣嗎?
List> 表示未知類型的列表,而 List
上界通配符 < ? extends E>在類型參數中使用 extends 表示這個泛型中的參數必須是 E 或者 E 的子類,這樣有兩個好處:
如果傳入的類型不是 E 或者 E 的子類,編譯不成功
泛型中調用使用 E 的方法,要不然必須強轉成 E 才能調用
下界通配符 < ? super E>在類型參數中使用 super 表示這個泛型中的參數必須是 E 或者 E 的父類。
privatevoid add(List super E> dst, List src){ for (E e : src) { dst.add(e); } }
dst 類型 “大于等于” src 的類型,這里的“大于等于”是指 dst 表示的范圍比 src 要大,因此裝得下 dst 的容器也就能裝 src。向上轉型總是合理的。
通配符比較無限制通配符 > 和 Object 有些相似,用于表示無限制或者不確定范圍的場景。> 相當于 extends Object> 。
< ? extends E> 用于靈活讀取,使得方法可以讀取 E 或 E 的任意子類型的容器對象。
< ? super E> 用于靈活寫入或比較,使得對象可以寫入父類型的容器,使得父類型的比較方法可以應用于子類對象。
為了獲得最大限度的靈活性,要在表示 生產者或者消費者 的輸入參數上使用通配符,使用的規則就是:生產者有上限、消費者有下限:PECS: producer-extends, costumer-super。
T 的生產者的意思就是結果會返回 T (get),這就要求返回一個具體的類型,必須有上限才夠具體;
T 的消費者的意思是要操作 T (set),這就要求操作的容器要夠大,所以容器需要是 T 的父類,即 super T;
private> E max(List extends E> e1){ if (e1 == null){ return null; } Iterator extends E> iterator = e1.iterator(); E result = iterator.next(); while (iterator.hasNext()){ E next = iterator.next(); if (next.compareTo(result) > 0){ result = next; } } return result; }
要進行比較,所以 E 需要是可比較的類,因此需要 extends Comparable<…>
Comparable super E> 要對 E 進行比較,即 E 的消費者,所以需要用 super
而參數 List extends E> 表示要操作的數據是 E 的子類的列表。
類型擦除Java 中的泛型和 C++ 中的模板有一個很大的不同:
C++ 中模板的實例化會為每一種類型都產生一套不同的代碼,這就是所謂的代碼膨脹。
Java 中并不會產生這個問題。虛擬機中并沒有泛型類型對象,所有的對象都是普通類。
在 Java 中,泛型是 Java 編譯器的概念,用泛型編寫的 Java 程序和普通的 Java程序基本相同,只是多了一些參數化的類型同時少了一些類型轉換。
實際上泛型程序也是首先被轉化成一般的、不帶泛型的 Java 程序后再進行處理的,編譯器自動完成了從 Generic Java 到普通Java 的翻譯,Java 虛擬機運行時對泛型基本一無所知。
當編譯器對帶有泛型的java代碼進行編譯時,它會去執行類型檢查和類型推斷,然后生成普通的不帶泛型的字節碼,這種普通的字節碼可以被一般的 Java 虛擬機接收并執行,這在就叫做類型擦除(type erasure)。
實際上無論你是否使用泛型,集合框架中存放對象的數據類型都是 Object。
Liststrings = new ArrayList<>(); List integers = new ArrayList<>(); System.out.println(strings.getClass() == integers.getClass());//true
上面代碼輸出結果并不是預期的 false,而是 true。其原因就是泛型的擦除。
實現原理Java 編輯器會將泛型代碼中的類型完全擦除,使其變成原始類型。
接著 Java編譯器會在這些代碼中加入類型轉換,將原始類型轉換成想要的類型。這些操作都是編譯器后臺進行,可以保證類型安全。
泛型就是一個語法糖,它運行時沒有存儲任何類型信息。
擦除導致的泛型不可變性協變:如果 A 是 B 的父類,并且 A 的容器(比如 List< A>)也是 B 的容(List< B > ) 的父類,則稱之為協變的(父子關系保持一致)
逆變:如果 A 是 B 的父類,但是 A 的容器 是 B 的容器的子類,則稱之為逆變(放入容器就篡位了)
不可變:不論 A B 有什么關系,A 的容器和 B 的容器都沒有父子關系,稱之為不可變。
Java 中數組是協變的,泛型是不可變的。
如果想要讓某個泛型類具有協變性,就需要用到邊界。
總結泛型是通過擦除來實現的。因此泛型只在編譯時強化它的類型信息,而在運行時丟棄(或者擦除)它的元素類型信息。擦除使得使用泛型的代碼可以和沒有使用泛型的代碼隨意互用。
數組中不能使用泛型
Java 中 List
在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數的類型進行檢查
通過使用 Object 作為類型,可以告知編譯器該方法可以接受任何類型的對象,比如String 或 Integer
可以把任何帶參數的類型傳遞給原始類型 List,但卻不能把 List< String> 傳遞給接受 List< Object>的方法,因為泛型的不可變性,會產生編譯錯誤。
區分原始類型和泛型變量的類型在調用泛型方法的時候,可以指定泛型,也可以不指定泛型。
在不指定泛型的情況下,泛型變量的類型為該方法中的幾種類型的同一個父類的最小級,直到Object。
在指定泛型的時候,該方法中的幾種類型必須是該泛型實例類型或者其子類。
public class Test2{ public static void main(String[] args) { /**不指定泛型的時候*/ int i=Test2.add(1, 2); //這兩個參數都是Integer,所以T為Integer類型 Number f=Test2.add(1, 1.2);//這兩個參數一個是Integer,以風格是Float,所以取同一父類的最小級,為Number Object o=Test2.add(1, "asd");//這兩個參數一個是Integer,以風格是Float,所以取同一父類的最小級,為Object /**指定泛型的時候*/ int a=Test2.add(1, 2);//指定了Integer,所以只能為Integer類型或者其子類 int b=Test2. add(1, 2.2);//編譯錯誤,指定了Integer,不能為Float Number c=Test2. add(1, 2.2); //指定為Number,所以可以為Integer和Float } //這是一個簡單的泛型方法 public static T add(T x,T y){ return y; } }
類型檢查就是針對引用的,誰是一個引用,用這個引用調用泛型方法,就會對這個引用調用的方法進行類型檢測,而無關它真正引用的對象。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/67941.html
摘要:引用泛型除了方法因不能使用外部實例參數外,其他繼承實現成員變量,成員方法,方法返回值等都可使用。因此,生成的字節碼僅包含普通的類,接口和方法。 為什么要使用泛型程序設計? 一般的類和方法,只能使用具體的類型:要么是基本類型,要么是自定義類的對應類型;如果要編寫可以應用于多種類型的代碼,這種刻板的限制對代碼的束縛就會很大。----摘自原書Ordinary classes and meth...
摘要:知識點總結泛型知識點總結泛型泛型泛型就是參數化類型適用于多種數據類型執行相同的代碼泛型中的類型在使用時指定泛型歸根到底就是模版優點使用泛型時,在實際使用之前類型就已經確定了,不需要強制類型轉換。 Java知識點總結(Java泛型) @(Java知識點總結)[Java, Java泛型] [toc] 泛型 泛型就是參數化類型 適用于多種數據類型執行相同的代碼 泛型中的類型在使用時指定 泛...
摘要:靜態變量是被泛型類的所有實例所共享的。所以引用能完成泛型類型的檢查。對于這個類型系統,有如下的一些規則相同類型參數的泛型類的關系取決于泛型類自身的繼承體系結構。事實上,泛型類擴展都不合法。 前言 和C++以模板來實現靜多態不同,Java基于運行時支持選擇了泛型,兩者的實現原理大相庭徑。C++可以支持基本類型作為模板參數,Java卻只能接受類作為泛型參數;Java可以在泛型類的方法中取得...
摘要:泛型類在類的申明時指定參數,即構成了泛型類。換句話說,泛型類可以看成普通類的工廠。的作用就是指明泛型的具體類型,而類型的變量,可以用來創建泛型類的對象。只有聲明了的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法。 什么是泛型? 泛型是JDK 1.5的一項新特性,它的本質是參數化類型(Parameterized Type)的應用,也就是說所操作的數據類型被指定為一個參數,...
摘要:可以看到,如果我們給泛型類制定了上限,泛型擦除之后就會被替換成類型的上限。相應的,泛型類中定義的方法的類型也是如此。參考語言類型擦除下界通配符和的區別 本篇博客主要介紹了Java類型擦除的定義,詳細的介紹了類型擦除在Java中所出現的場景。 1. 什么是類型擦除 為了讓你們快速的對類型擦除有一個印象,首先舉一個很簡單也很經典的例子。 // 指定泛型為String List list1 ...
閱讀 2608·2023-04-25 22:09
閱讀 2844·2021-10-14 09:47
閱讀 1935·2021-10-11 11:10
閱讀 2688·2021-10-09 09:44
閱讀 3383·2021-09-22 14:57
閱讀 2502·2019-08-30 15:56
閱讀 1620·2019-08-30 15:55
閱讀 781·2019-08-30 14:13