一、泛型基礎(chǔ)
1)润文、什么示泛型
Java泛型是JDK1.5中引入的一個新特性宽闲,泛型提供了編譯時類型安全檢查機制丘侠,該機制允許程序員在編譯時檢到非法類型庐扫。
泛型的本質(zhì)是參數(shù)類型遵湖,也就是說所操作的數(shù)據(jù)類型指定為一個參數(shù)類型缺亮。
泛型不存在與JNM虛擬機翁涤。
2)、為什么使用泛型
1、泛型可以增強
編譯時
錯誤檢查葵礼,減少因類型問題引發(fā)的運行異常(ClassCastException)号阿,因此泛型具有更強的類型檢查。
2鸳粉、泛型可以避免類型轉(zhuǎn)換扔涧。
//沒使用泛型
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疼约、泛型類
泛型類格式:
class name <T1,T2,...Tn>{}
泛型類之影響內(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)
//泛型類, 泛型接口的使用
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和類泛型中的T不是一個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)劝枣、常見類型變量名稱
- E : 表示
集合元素
類型(在Java集合框架中廣泛運用) - K : 表示
關(guān)鍵字
類型 - N:表示
數(shù)字
類型 - V : 表示
值
類型 - T : 表示
任意
類型 - S,U织鲸,V:第二舔腾,第三,第四個類型
- ? :通配符搂擦,不能用在類中稳诚,可應(yīng)用在方法和參數(shù)
5)、原始類型
缺少實際類型變量的泛型就是一個原始類型
如:
java
class Box<T>{}
Box box = new Box(); //這個Box就是Box<T>的原始類型
kotlin
class GG<T>
val gg: GG<*> = GG<Any?>()
二瀑踢、泛型限制
對泛型變量的范圍作出限制
1)扳还、單一限制
<T extends X> //表示類型的上界,類型參數(shù)是X的子類
<T super X> //表示類型的下界橱夭,類型參數(shù)是X的超類
2)氨距、多種限制
<T extends A & B & C>
- extends 表達(dá)的意識:這里指的是廣義上的擴展,兼有
類型繼承
和接口實現(xiàn)
之意棘劣。 - 多種限制下的格式語法要求:如果上限類型是一個類俏让,必須第一位標(biāo)識出,否著編譯錯誤茬暇。且只能由一個類首昔,多個接口
泛型算法實現(xiàn)的關(guān)鍵:利用受限類型參數(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和子類沙廉,獲取對象是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 超過了上線
GenericType<? extends Fruit> genericType = new GenericType<>();
genericType.setDate(new Apple()); //error 不能設(shè)置數(shù)據(jù)
Fruit date = genericType.getDate(); //只能訪問數(shù)據(jù)
}
public static void use2(){
print2(new GenericType<Apple>()); //true
print2(new GenericType<Fruit>()); //true
print2(new GenericType<HongFuShi>()); //error 超過線下
print2(new GenericType<Orange>()); //error 不是同一個類型
//因為 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類型
}
}
四撬陵、泛型擦除
功能:保證了泛型不在運行時出現(xiàn)
類型消除應(yīng)用場合
-
編譯器會把泛型類型中所有的類型參數(shù)替換成他們的上(下)限
珊皿,
如果沒有對應(yīng)類型作出限制,那么就會替換成Object類型巨税。因此蟋定,編譯出的字節(jié)碼僅僅包含常規(guī)類,接口和方法草添。 - 多種限制<T extends A & B & C>擦除后用A驶兜。
- 在必要的時候插入類型轉(zhuǎn)換以保證類型安全。
-
生成橋方法
以在擴展泛型時保持多態(tài)性远寸。
Bridge Methods 橋方法 - 當(dāng)編譯一個擴展參數(shù)化類的類抄淑,或一個實現(xiàn)參數(shù)化類型接口的接口時,編譯器有可能會創(chuàng)建一個合成方法驰后,名為橋方法肆资。它是類型擦除過程的一部分。
- java 的泛型偽泛型灶芝,JVM中不支持泛型郑原,為了兼容低版本(JDK1.5以下)
五、編譯
-
1)夜涕、用javac把java文件編譯成class文件
javac DD.java
-
2)犯犁、用javap反編譯class文件字節(jié)碼
javap -c DD.class
六、知識點
1女器、) ArrayList<String> 酸役、ArrayList<Object>和ArrayList<?>是否可以相互轉(zhuǎn)化
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
ArrayList<?> list3 = new ArrayList<>();
//不能同一類型,雖然String是Object的子類驾胆,但是ArrayList<String> 涣澡、ArrayList<Object>不是同一類型
list2 = list1;
//可以直接轉(zhuǎn)換,因為俏拱?是通配符
list3 = list1;
但是泛型類可以繼承或者擴展其他泛型類,比如List和ArrayList
2吼句、) 限制
class D<T> {
private T data;
public D() {
//不能實例化類型變量
this.data = new T(); //error
}
//泛型類的靜態(tài)上下文中類型變量失效锅必, 所以靜態(tài)域或方法里不能引用類型變量
//引文不知道泛型類型,泛型類型是做類初始化時候才知道的
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 不能捕獲泛型類的實例
}
}
//true 泛型可以拋出異常
private <V extends Exception> void doWork2(V v) throws V{
try {
} catch (Exception e){
throw v;
}
}
public static void main(String[] argc){
//不能用基本類型實例化類型參數(shù)
D<double> d1; //error
//包裝類型可以作為泛型
D<Double> d2 = new D<>(); //true
//運行時類型查詢只適用于原始類型
//泛型不能用 instanceof
if (d1 instanceof D<Double>){} //error
if (d1 instanceof D<T>){} //error
D<String> d3 = new D<>();
//因為泛型擦除惕艳,獲取泛型的原生類型進(jìn)行比較
System.out.println(d2.getClass() == d3.getClass()); //true
//不能創(chuàng)建參數(shù)化類型的數(shù)組
//可以定義泛型數(shù)組搞隐,但不能new一個泛型數(shù)組
D<String>[] d4; //true
D<String>[] d5 = new D<String>[10]; //error
}
}
//泛型不能繼承異常
class A<T> extends Exception { //error
}
//無法創(chuàng)建類型化實例
private static <E> void append(List<E> list) throws Exception{
E elem = new E(); //compile-time error
list.add(elem);
}
//通過反射創(chuàng)建一個參數(shù)化類型實例
private static <E> void append(List<E> list, Class<E> clazz) throws Exception{
E e = clazz.newInstance();
list.add(e);
}
七、虛擬機是如何實現(xiàn)泛型的
- 泛型思想早在C++語言的模板(Template)中就開始生根發(fā)芽远搪,在Java語言處于還沒有出現(xiàn)泛型的版本時劣纲,只能通過Object是所有類型的父類和類型強制轉(zhuǎn)換兩個特點的配合來實現(xiàn)類型泛化。谁鳍,由于Java語言里面所有的類型都繼承于java.lang.Object癞季,所以O(shè)bject轉(zhuǎn)型成任何對象都是有可能的劫瞳。但是也因為有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object到底是個什么類型的對象绷柒。在編譯期間志于,編譯器無法檢查這個Object的強制轉(zhuǎn)型是否成功,如果僅僅依賴程序員去保障這項操作的正確性废睦,許多ClassCastException的風(fēng)險就會轉(zhuǎn)嫁到程序運行期之中伺绽。
- 泛型技術(shù)在C#和Java之中的使用方式看似相同,但實現(xiàn)上卻有著根本性的分歧嗜湃,C#里面泛型無論在程序源碼中奈应、編譯后的IL中(Intermediate Language,中間語言购披,這時候泛型是一個占位符)杖挣,或是運行期的CLR中,都是切實存在的今瀑,List<int>與List<String>就是兩個不同的類型程梦,它們在系統(tǒng)運行期生成,有自己的虛方法表和類型數(shù)據(jù)橘荠,這種實現(xiàn)稱為類型膨脹屿附,基于這種方法實現(xiàn)的泛型稱為真實泛型。
- Java語言中的泛型則不一樣哥童,它只在程序源碼中存在挺份,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來的原生類型(Raw Type贮懈,也稱為裸類型)了匀泊,并且在相應(yīng)的地方插入了強制轉(zhuǎn)型代碼,因此朵你,對于運行期的Java語言來說各聘,ArrayList<int>與ArrayList<String>就是同一個類,所以泛型技術(shù)實際上是Java語言的一顆語法糖抡医,Java語言中的泛型實現(xiàn)方法稱為類型擦除躲因,基于這種方法實現(xiàn)的泛型稱為偽泛型。
- 將一段Java代碼編譯成Class文件忌傻,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后大脉,將會發(fā)現(xiàn)泛型都不見了,程序又變回了Java泛型出現(xiàn)之前的寫法水孩,泛型類型都變回了原生類型
public static String method(List<String> str){
return "ok";
}
public static Integer method(List<Integer> str){
return 1;
}
- 開發(fā)工具編譯器:檢測是否為同一個類類型镰矿,檢測方法名和參數(shù)是否相同。
- JDK編譯器:檢測是否為同一個類類型俘种,檢測方法名秤标、返回類型和參數(shù)是否相同绝淡。
解析:在開發(fā)工具編譯器中是同一個類型,因為參數(shù)List<T>擦除后是Object對象抛杨,所以參數(shù)相同够委。JDK編譯器 中不是同種方法,因為返回類型不同怖现。
- 上面這段代碼是不能被編譯的茁帽,因為參數(shù)List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類型List<E>屈嗤,擦除動作導(dǎo)致這兩種方法的特征簽名變得一模一樣潘拨。
- 由于Java泛型的引入,各種場景(虛擬機解析饶号、反射等)下的方法調(diào)用都可能對原有的基礎(chǔ)產(chǎn)生影響和新的需求铁追,如在泛型類中如何獲取傳入的參數(shù)化類型等。因此茫船,JCP組織對虛擬機規(guī)范做出了相應(yīng)的修改琅束,引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來的參數(shù)類型的識別問題算谈,Signature是其中最重要的一項屬性涩禀,它的作用就是存儲一個方法在字節(jié)碼層面的特征簽名,這個屬性中保存的參數(shù)類型并不是原生類型然眼,而是包括了參數(shù)化類型的信息艾船。修改后的虛擬機規(guī)范要求所有能識別49.0以上版本的Class文件的虛擬機都要能正確地識別Signature參數(shù)。
- 另外高每,從Signature屬性的出現(xiàn)我們還可以得出結(jié)論屿岂,擦除法所謂的擦除,僅僅是對方法的Code屬性中的字節(jié)碼進(jìn)行擦除鲸匿,實際上元數(shù)據(jù)中還是保留了泛型信息爷怀,這也是我們能通過反射手段取得參數(shù)化類型的根本依據(jù)。
八带欢、泛型擦除后恢復(fù)實例
因為在編譯成CLASS是运授,在JVM有個
Signature
會對泛型弱記憶;然后可以Type
類中設(shè)置泛型類型洪囤,從而找到Signature 中記憶的泛型類型徒坡。
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
* 有花括號{}:代表匿名內(nèi)部類,創(chuàng)建一個匿名內(nèi)部內(nèi)實力對向
* 無花括號{}:創(chuàng)建實例對象
*/
Response<Date> date = gson.fromJson(str, new TypeRefrence<Response<Date>>(){}.getType());
System.out.println(date.toString());
3)伦泥、自定義gson的TypeToken類型
/**
* 當(dāng)構(gòu)造方法為protected剥啤, 或者 abstract class 創(chuàng)建時必須帶花括號{}
*
* @param <T>
*/
abstract class TypeRefrence<T> {
Type type;
protected TypeRefrence() {
//獲得泛型類型
Type genericSuperclass = getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//因為泛型類型可以定以多個A<T, v ...>所以是數(shù)組
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
type = actualTypeArguments[0];
}
public Type getType() {
return type;
}
}
4)锦溪、type類型子接口
-
TypeVariable
:泛型類型變量,可以泛型上下限等信息 -
ParameterizedType
:具體的泛型類型府怯,可以獲取元數(shù)據(jù)中泛型簽名類型(泛型真實類型)刻诊。 -
GenericArrayType
:當(dāng)需要描述的泛型是泛型類數(shù)組時,比如List[],Map[]牺丙,此接口會作為Type的實現(xiàn)则涯。 -
WildcardType
:通配符泛型,獲取上下限信息冲簿。