一、泛型基礎(chǔ)
1)、什么示泛型
Java泛型是JDK1.5中引入的一個(gè)新特性,泛型提供了編譯時(shí)類(lèi)型安全檢查機(jī)制,該機(jī)制允許程序員在編譯時(shí)檢到非法類(lèi)型。
泛型的本質(zhì)是參數(shù)類(lèi)型,也就是說(shuō)所操作的數(shù)據(jù)類(lèi)型指定為一個(gè)參數(shù)類(lèi)型。
泛型不存在與JNM虛擬機(jī)。
2)、為什么使用泛型
1、泛型可以增強(qiáng)
編譯時(shí)錯(cuò)誤檢查,減少因類(lèi)型問(wèn)題引發(fā)的運(yùn)行異常(ClassCastException),因此泛型具有更強(qiáng)的類(lèi)型檢查。
2、泛型可以避免類(lèi)型轉(zhuǎn)換。
//沒(méi)使用泛型
private void fun1(){
List nameList = new ArrayList();
nameList.add("XaoMing");
String name = (String) nameList.get(0);
}
//使用泛型
private void fun2(){
List<String> nameList = new ArrayList();
nameList.add("XaoMing");
String name = nameList.get(0);
}
3、泛型可以泛型算法,增加代碼復(fù)用性。
private <T extends Comparable<T>> int countComparable(T[] array, T t2) {
int count = 0;
for (T e : array){
if (e.compareTo(t2) > 0){
count ++;
}
}
return count;
}
3)、Java中的泛型
1、泛型類(lèi)
泛型類(lèi)格式:
class name <T1,T2,...Tn>{}
泛型類(lèi)之影響內(nèi)部的普通方法和變量
2、泛型接口
泛型格式:
interface NameInterface<T> {}
3、泛型方法
定義格式:
private <K,V> boolean compare(Pair<K, V> p1, Pare<K, V>p2){}
調(diào)用格式:
Util.<K, V> compare(p1, p2)
//泛型類(lèi), 泛型接口的使用
class Men1<T, V> : MenInterface<V>{
var name: T
var str : V? = null
constructor(name: T) {
this.name = name
}
//泛型方法
fun <E> getStr1 (str: E) {
}
//泛型方法, 其中的T和類(lèi)泛型中的T不是一個(gè)T
fun <T> getStr2 (str: T) {
}
private fun <T : Comparable<T>?> countComparable(array: Array<T>, t2: T): Int {
var count = 0
for (e in array) {
if (e!! > t2) {
count++
}
}
return count
}
override fun getMen(): V {
return str!!
}
override fun setMen(men: V) {
this.str = men
}
}
//泛型接口的使用
class Men2 : MenInterface<String>{
override fun getMen(): String {
return "XaoMing"
}
override fun setMen(men: String) {
TODO("Not yet implemented")
}
}
//泛型接口
interface MenInterface<V>{
fun getMen() : V
fun setMen(men : V)
}
4)、常見(jiàn)類(lèi)型變量名稱(chēng)
- E : 表示
集合元素類(lèi)型(在Java集合框架中廣泛運(yùn)用) - K : 表示
關(guān)鍵字類(lèi)型 - N:表示
數(shù)字類(lèi)型 - V : 表示
值類(lèi)型 - T : 表示
任意類(lèi)型 - S,U,V:第二,第三,第四個(gè)類(lèi)型
- ? :通配符,不能用在類(lèi)中,可應(yīng)用在方法和參數(shù)
5)、原始類(lèi)型
缺少實(shí)際類(lèi)型變量的泛型就是一個(gè)原始類(lèi)型
如:
java
class Box<T>{}
Box box = new Box(); //這個(gè)Box就是Box<T>的原始類(lèi)型
kotlin
class GG<T>
val gg: GG<*> = GG<Any?>()
二、泛型限制
對(duì)泛型變量的范圍作出限制
1)、單一限制
<T extends X> //表示類(lèi)型的上界,類(lèi)型參數(shù)是X的子類(lèi)
<T super X> //表示類(lèi)型的下界,類(lèi)型參數(shù)是X的超類(lèi)
2)、多種限制
<T extends A & B & C>
- extends 表達(dá)的意識(shí):這里指的是廣義上的擴(kuò)展,兼有
類(lèi)型繼承和接口實(shí)現(xiàn)之意。 - 多種限制下的格式語(yǔ)法要求:如果上限類(lèi)型是一個(gè)類(lèi),必須第一位標(biāo)識(shí)出,否著編譯錯(cuò)誤。且只能由一個(gè)類(lèi),多個(gè)接口
泛型算法實(shí)現(xiàn)的關(guān)鍵:利用受限類(lèi)型參數(shù)
public class A {}
public interface B {}
public interface C {}
public class D<T extends A & B & C> { }
public static <T extends A & B & C> void setData(T data) { }
kotlin
interface A{
fun setName()
}
interface B{
fun setSex()
}
class D<T> where T: A, T: B {
}
三、PESC原則
//獲取的值是Number,不能設(shè)置值
List<? super Number> list1;
//設(shè)置Number和子類(lèi),獲取對(duì)象是Object
List<? extends Object> list2;
class Foot{}
class Fruit extends Foot {}
class Apple extends Fruit {}
class HongFuShi extends Apple {}
class Orange extends Fruit {}
class GenericType<T>{
private T date;
public T getDate() {
return date;
}
public void setDate(T date) {
this.date = date;
}
}
class FruitTest{
public static void print1(GenericType<? extends Fruit> fruit){
System.out.println(fruit.getDate().toString());
}
public static void print2(GenericType<? super Apple> apple){
System.out.println(apple.getDate().toString());
}
public static void use1(){
print1(new GenericType<Fruit>()); //true
print1(new GenericType<Apple>()); //true
print1(new GenericType<HongFuShi>()); //true
print1(new GenericType<Foot>()); //error 超過(guò)了上線
GenericType<? extends Fruit> genericType = new GenericType<>();
genericType.setDate(new Apple()); //error 不能設(shè)置數(shù)據(jù)
Fruit date = genericType.getDate(); //只能訪問(wèn)數(shù)據(jù)
}
public static void use2(){
print2(new GenericType<Apple>()); //true
print2(new GenericType<Fruit>()); //true
print2(new GenericType<HongFuShi>()); //error 超過(guò)線下
print2(new GenericType<Orange>()); //error 不是同一個(gè)類(lèi)型
//因?yàn)?Apple和下限HongFuShi可以安全轉(zhuǎn)型為Apple, Fruit不能安全轉(zhuǎn)型
GenericType<? super Apple> genericType = new GenericType<>();
genericType.setDate(new Apple()); //true
genericType.setDate(new HongFuShi()); //true
genericType.setDate(new Fruit()); //error super 設(shè)置數(shù)據(jù),只能設(shè)置自己和下限
Object date = genericType.getDate(); //獲取的數(shù)據(jù)是Object類(lèi)型
}
}
四、泛型擦除
功能:保證了泛型不在運(yùn)行時(shí)出現(xiàn)
類(lèi)型消除應(yīng)用場(chǎng)合
-
編譯器會(huì)把泛型類(lèi)型中所有的類(lèi)型參數(shù)替換成他們的上(下)限
,如果沒(méi)有對(duì)應(yīng)類(lèi)型作出限制,那么就會(huì)替換成Object類(lèi)型。因此,編譯出的字節(jié)碼僅僅包含常規(guī)類(lèi),接口和方法。 - 多種限制<T extends A & B & C>擦除后用A。
- 在必要的時(shí)候插入類(lèi)型轉(zhuǎn)換以保證類(lèi)型安全。
-
生成橋方法以在擴(kuò)展泛型時(shí)保持多態(tài)性。
Bridge Methods 橋方法 - 當(dāng)編譯一個(gè)擴(kuò)展參數(shù)化類(lèi)的類(lèi),或一個(gè)實(shí)現(xiàn)參數(shù)化類(lèi)型接口的接口時(shí),編譯器有可能會(huì)創(chuàng)建一個(gè)合成方法,名為橋方法。它是類(lèi)型擦除過(guò)程的一部分。
- java 的泛型偽泛型,JVM中不支持泛型,為了兼容低版本(JDK1.5以下)
五、編譯
-
1)、用javac把java文件編譯成class文件
javac DD.java
-
2)、用javap反編譯class文件字節(jié)碼
javap -c DD.class
六、知識(shí)點(diǎn)
1、) ArrayList<String> 、ArrayList<Object>和ArrayList<?>是否可以相互轉(zhuǎn)化
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
ArrayList<?> list3 = new ArrayList<>();
//不能同一類(lèi)型,雖然String是Object的子類(lèi),但是ArrayList<String> 、ArrayList<Object>不是同一類(lèi)型
list2 = list1;
//可以直接轉(zhuǎn)換,因?yàn)??是通配?list3 = list1;
但是泛型類(lèi)可以繼承或者擴(kuò)展其他泛型類(lèi),比如List和ArrayList

2、) 限制
class D<T> {
private T data;
public D() {
//不能實(shí)例化類(lèi)型變量
this.data = new T(); //error
}
//泛型類(lèi)的靜態(tài)上下文中類(lèi)型變量失效, 所以靜態(tài)域或方法里不能引用類(lèi)型變量
//引文不知道泛型類(lèi)型,泛型類(lèi)型是做類(lèi)初始化時(shí)候才知道的
private static T instance1(){} //error
//靜態(tài)方法是放行方法可以
private static <T>T instance2(T data){ //true
return data;
}
//error
private <V extends Exception> void doWork1(V v){
try {
} catch (T e){ //error 不能捕獲泛型類(lèi)的實(shí)例
}
}
//true 泛型可以拋出異常
private <V extends Exception> void doWork2(V v) throws V{
try {
} catch (Exception e){
throw v;
}
}
public static void main(String[] argc){
//不能用基本類(lèi)型實(shí)例化類(lèi)型參數(shù)
D<double> d1; //error
//包裝類(lèi)型可以作為泛型
D<Double> d2 = new D<>(); //true
//運(yùn)行時(shí)類(lèi)型查詢(xún)只適用于原始類(lèi)型
//泛型不能用 instanceof
if (d1 instanceof D<Double>){} //error
if (d1 instanceof D<T>){} //error
D<String> d3 = new D<>();
//因?yàn)榉盒筒脸?,獲取泛型的原生類(lèi)型進(jìn)行比較
System.out.println(d2.getClass() == d3.getClass()); //true
//不能創(chuàng)建參數(shù)化類(lèi)型的數(shù)組
//可以定義泛型數(shù)組,但不能new一個(gè)泛型數(shù)組
D<String>[] d4; //true
D<String>[] d5 = new D<String>[10]; //error
}
}
//泛型不能繼承異常
class A<T> extends Exception { //error
}
//無(wú)法創(chuàng)建類(lèi)型化實(shí)例
private static <E> void append(List<E> list) throws Exception{
E elem = new E(); //compile-time error
list.add(elem);
}
//通過(guò)反射創(chuàng)建一個(gè)參數(shù)化類(lèi)型實(shí)例
private static <E> void append(List<E> list, Class<E> clazz) throws Exception{
E e = clazz.newInstance();
list.add(e);
}
七、虛擬機(jī)是如何實(shí)現(xiàn)泛型的
- 泛型思想早在C++語(yǔ)言的模板(Template)中就開(kāi)始生根發(fā)芽,在Java語(yǔ)言處于還沒(méi)有出現(xiàn)泛型的版本時(shí),只能通過(guò)Object是所有類(lèi)型的父類(lèi)和類(lèi)型強(qiáng)制轉(zhuǎn)換兩個(gè)特點(diǎn)的配合來(lái)實(shí)現(xiàn)類(lèi)型泛化。,由于Java語(yǔ)言里面所有的類(lèi)型都繼承于java.lang.Object,所以O(shè)bject轉(zhuǎn)型成任何對(duì)象都是有可能的。但是也因?yàn)橛袩o(wú)限的可能性,就只有程序員和運(yùn)行期的虛擬機(jī)才知道這個(gè)Object到底是個(gè)什么類(lèi)型的對(duì)象。在編譯期間,編譯器無(wú)法檢查這個(gè)Object的強(qiáng)制轉(zhuǎn)型是否成功,如果僅僅依賴(lài)程序員去保障這項(xiàng)操作的正確性,許多ClassCastException的風(fēng)險(xiǎn)就會(huì)轉(zhuǎn)嫁到程序運(yùn)行期之中。
- 泛型技術(shù)在C#和Java之中的使用方式看似相同,但實(shí)現(xiàn)上卻有著根本性的分歧,C#里面泛型無(wú)論在程序源碼中、編譯后的IL中(Intermediate Language,中間語(yǔ)言,這時(shí)候泛型是一個(gè)占位符),或是運(yùn)行期的CLR中,都是切實(shí)存在的,List<int>與List<String>就是兩個(gè)不同的類(lèi)型,它們?cè)谙到y(tǒng)運(yùn)行期生成,有自己的虛方法表和類(lèi)型數(shù)據(jù),這種實(shí)現(xiàn)稱(chēng)為類(lèi)型膨脹,基于這種方法實(shí)現(xiàn)的泛型稱(chēng)為真實(shí)泛型。
- Java語(yǔ)言中的泛型則不一樣,它只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來(lái)的原生類(lèi)型(Raw Type,也稱(chēng)為裸類(lèi)型)了,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此,對(duì)于運(yùn)行期的Java語(yǔ)言來(lái)說(shuō),ArrayList<int>與ArrayList<String>就是同一個(gè)類(lèi),所以泛型技術(shù)實(shí)際上是Java語(yǔ)言的一顆語(yǔ)法糖,Java語(yǔ)言中的泛型實(shí)現(xiàn)方法稱(chēng)為類(lèi)型擦除,基于這種方法實(shí)現(xiàn)的泛型稱(chēng)為偽泛型。
- 將一段Java代碼編譯成Class文件,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后,將會(huì)發(fā)現(xiàn)泛型都不見(jiàn)了,程序又變回了Java泛型出現(xiàn)之前的寫(xiě)法,泛型類(lèi)型都變回了原生類(lèi)型
public static String method(List<String> str){
return "ok";
}
public static Integer method(List<Integer> str){
return 1;
}
- 開(kāi)發(fā)工具編譯器:檢測(cè)是否為同一個(gè)類(lèi)類(lèi)型,檢測(cè)方法名和參數(shù)是否相同。
- JDK編譯器:檢測(cè)是否為同一個(gè)類(lèi)類(lèi)型,檢測(cè)方法名、返回類(lèi)型和參數(shù)是否相同。
解析:在開(kāi)發(fā)工具編譯器中是同一個(gè)類(lèi)型,因?yàn)閰?shù)List<T>擦除后是Object對(duì)象,所以參數(shù)相同。JDK編譯器 中不是同種方法,因?yàn)榉祷仡?lèi)型不同。
- 上面這段代碼是不能被編譯的,因?yàn)閰?shù)List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類(lèi)型List<E>,擦除動(dòng)作導(dǎo)致這兩種方法的特征簽名變得一模一樣。
- 由于Java泛型的引入,各種場(chǎng)景(虛擬機(jī)解析、反射等)下的方法調(diào)用都可能對(duì)原有的基礎(chǔ)產(chǎn)生影響和新的需求,如在泛型類(lèi)中如何獲取傳入的參數(shù)化類(lèi)型等。因此,JCP組織對(duì)虛擬機(jī)規(guī)范做出了相應(yīng)的修改,引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來(lái)的參數(shù)類(lèi)型的識(shí)別問(wèn)題,Signature是其中最重要的一項(xiàng)屬性,它的作用就是存儲(chǔ)一個(gè)方法在字節(jié)碼層面的特征簽名,這個(gè)屬性中保存的參數(shù)類(lèi)型并不是原生類(lèi)型,而是包括了參數(shù)化類(lèi)型的信息。修改后的虛擬機(jī)規(guī)范要求所有能識(shí)別49.0以上版本的Class文件的虛擬機(jī)都要能正確地識(shí)別Signature參數(shù)。
- 另外,從Signature屬性的出現(xiàn)我們還可以得出結(jié)論,擦除法所謂的擦除,僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦除,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息,這也是我們能通過(guò)反射手段取得參數(shù)化類(lèi)型的根本依據(jù)。
八、泛型擦除后恢復(fù)實(shí)例
因?yàn)樵诰幾g成CLASS是,在JVM有個(gè)
Signature會(huì)對(duì)泛型弱記憶;然后可以Type類(lèi)中設(shè)置泛型類(lèi)型,從而找到Signature 中記憶的泛型類(lèi)型。
1)、導(dǎo)入google 的 gson 原理
implementation 'com.google.code.gson:gson:2.6.2'
2)、代碼
Response<Date> dateResponse = new Response<>("200", "true", new Date("XaoHua"));
String str = gson.toJson(dateResponse);
System.out.println(str);
/**
* 用自定義的 TypeRefrence 代替 google 的 TypeToken
* 有花括號(hào){}:代表匿名內(nèi)部類(lèi),創(chuàng)建一個(gè)匿名內(nèi)部?jī)?nèi)實(shí)力對(duì)向
* 無(wú)花括號(hào){}:創(chuàng)建實(shí)例對(duì)象
*/
Response<Date> date = gson.fromJson(str, new TypeRefrence<Response<Date>>(){}.getType());
System.out.println(date.toString());
3)、自定義gson的TypeToken類(lèi)型
/**
* 當(dāng)構(gòu)造方法為protected, 或者 abstract class 創(chuàng)建時(shí)必須帶花括號(hào){}
*
* @param <T>
*/
abstract class TypeRefrence<T> {
Type type;
protected TypeRefrence() {
//獲得泛型類(lèi)型
Type genericSuperclass = getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//因?yàn)榉盒皖?lèi)型可以定以多個(gè)A<T, v ...>所以是數(shù)組
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
type = actualTypeArguments[0];
}
public Type getType() {
return type;
}
}
4)、type類(lèi)型子接口
-
TypeVariable:泛型類(lèi)型變量,可以泛型上下限等信息 -
ParameterizedType:具體的泛型類(lèi)型,可以獲取元數(shù)據(jù)中泛型簽名類(lèi)型(泛型真實(shí)類(lèi)型)。 -
GenericArrayType:當(dāng)需要描述的泛型是泛型類(lèi)數(shù)組時(shí),比如List[],Map[],此接口會(huì)作為T(mén)ype的實(shí)現(xiàn)。 -
WildcardType:通配符泛型,獲取上下限信息。

