一、泛型基礎
1)、什么示泛型
Java泛型是JDK1.5中引入的一個新特性,泛型提供了編譯時類型安全檢查機制,該機制允許程序員在編譯時檢到非法類型。
泛型的本質是參數類型,也就是說所操作的數據類型指定為一個參數類型。
泛型不存在與JNM虛擬機。
2)、為什么使用泛型
1、泛型可以增強
編譯時
錯誤檢查,減少因類型問題引發的運行異常(ClassCastException),因此泛型具有更強的類型檢查。
2、泛型可以避免類型轉換。
//沒使用泛型
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、泛型可以泛型算法,增加代碼復用性。
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>{}
泛型類之影響內部的普通方法和變量
2、泛型接口
泛型格式:
interface NameInterface<T> {}
3、泛型方法
定義格式:
private <K,V> boolean compare(Pair<K, V> p1, Pare<K, V>p2){}
調用格式:
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 : 表示
關鍵字
類型 - N:表示
數字
類型 - V : 表示
值
類型 - T : 表示
任意
類型 - S,U,V:第二,第三,第四個類型
- ? :通配符,不能用在類中,可應用在方法和參數
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> //表示類型的上界,類型參數是X的子類
<T super X> //表示類型的下界,類型參數是X的超類
2)、多種限制
<T extends A & B & C>
- extends 表達的意識:這里指的是廣義上的擴展,兼有
類型繼承
和接口實現
之意。 - 多種限制下的格式語法要求:如果上限類型是一個類,必須第一位標識出,否著編譯錯誤。且只能由一個類,多個接口
泛型算法實現的關鍵:利用受限類型參數
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,不能設置值
List<? super Number> list1;
//設置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 不能設置數據
Fruit date = genericType.getDate(); //只能訪問數據
}
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可以安全轉型為Apple, Fruit不能安全轉型
GenericType<? super Apple> genericType = new GenericType<>();
genericType.setDate(new Apple()); //true
genericType.setDate(new HongFuShi()); //true
genericType.setDate(new Fruit()); //error super 設置數據,只能設置自己和下限
Object date = genericType.getDate(); //獲取的數據是Object類型
}
}
四、泛型擦除
功能:保證了泛型不在運行時出現
類型消除應用場合
-
編譯器會把泛型類型中所有的類型參數替換成他們的上(下)限
,
如果沒有對應類型作出限制,那么就會替換成Object類型。因此,編譯出的字節碼僅僅包含常規類,接口和方法。 - 多種限制<T extends A & B & C>擦除后用A。
- 在必要的時候插入類型轉換以保證類型安全。
-
生成橋方法
以在擴展泛型時保持多態性。
Bridge Methods 橋方法 - 當編譯一個擴展參數化類的類,或一個實現參數化類型接口的接口時,編譯器有可能會創建一個合成方法,名為橋方法。它是類型擦除過程的一部分。
- java 的泛型偽泛型,JVM中不支持泛型,為了兼容低版本(JDK1.5以下)
五、編譯
-
1)、用javac把java文件編譯成class文件
javac DD.java
-
2)、用javap反編譯class文件字節碼
javap -c DD.class
六、知識點
1、) ArrayList<String> 、ArrayList<Object>和ArrayList<?>是否可以相互轉化
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
ArrayList<?> list3 = new ArrayList<>();
//不能同一類型,雖然String是Object的子類,但是ArrayList<String> 、ArrayList<Object>不是同一類型
list2 = list1;
//可以直接轉換,因為?是通配符
list3 = list1;
但是泛型類可以繼承或者擴展其他泛型類,比如List和ArrayList
2、) 限制
class D<T> {
private T data;
public D() {
//不能實例化類型變量
this.data = new T(); //error
}
//泛型類的靜態上下文中類型變量失效, 所以靜態域或方法里不能引用類型變量
//引文不知道泛型類型,泛型類型是做類初始化時候才知道的
private static T instance1(){} //error
//靜態方法是放行方法可以
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){
//不能用基本類型實例化類型參數
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<>();
//因為泛型擦除,獲取泛型的原生類型進行比較
System.out.println(d2.getClass() == d3.getClass()); //true
//不能創建參數化類型的數組
//可以定義泛型數組,但不能new一個泛型數組
D<String>[] d4; //true
D<String>[] d5 = new D<String>[10]; //error
}
}
//泛型不能繼承異常
class A<T> extends Exception { //error
}
//無法創建類型化實例
private static <E> void append(List<E> list) throws Exception{
E elem = new E(); //compile-time error
list.add(elem);
}
//通過反射創建一個參數化類型實例
private static <E> void append(List<E> list, Class<E> clazz) throws Exception{
E e = clazz.newInstance();
list.add(e);
}
七、虛擬機是如何實現泛型的
- 泛型思想早在C++語言的模板(Template)中就開始生根發芽,在Java語言處于還沒有出現泛型的版本時,只能通過Object是所有類型的父類和類型強制轉換兩個特點的配合來實現類型泛化。,由于Java語言里面所有的類型都繼承于java.lang.Object,所以Object轉型成任何對象都是有可能的。但是也因為有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object到底是個什么類型的對象。在編譯期間,編譯器無法檢查這個Object的強制轉型是否成功,如果僅僅依賴程序員去保障這項操作的正確性,許多ClassCastException的風險就會轉嫁到程序運行期之中。
- 泛型技術在C#和Java之中的使用方式看似相同,但實現上卻有著根本性的分歧,C#里面泛型無論在程序源碼中、編譯后的IL中(Intermediate Language,中間語言,這時候泛型是一個占位符),或是運行期的CLR中,都是切實存在的,List<int>與List<String>就是兩個不同的類型,它們在系統運行期生成,有自己的虛方法表和類型數據,這種實現稱為類型膨脹,基于這種方法實現的泛型稱為真實泛型。
- Java語言中的泛型則不一樣,它只在程序源碼中存在,在編譯后的字節碼文件中,就已經替換為原來的原生類型(Raw Type,也稱為裸類型)了,并且在相應的地方插入了強制轉型代碼,因此,對于運行期的Java語言來說,ArrayList<int>與ArrayList<String>就是同一個類,所以泛型技術實際上是Java語言的一顆語法糖,Java語言中的泛型實現方法稱為類型擦除,基于這種方法實現的泛型稱為偽泛型。
- 將一段Java代碼編譯成Class文件,然后再用字節碼反編譯工具進行反編譯后,將會發現泛型都不見了,程序又變回了Java泛型出現之前的寫法,泛型類型都變回了原生類型
public static String method(List<String> str){
return "ok";
}
public static Integer method(List<Integer> str){
return 1;
}
- 開發工具編譯器:檢測是否為同一個類類型,檢測方法名和參數是否相同。
- JDK編譯器:檢測是否為同一個類類型,檢測方法名、返回類型和參數是否相同。
解析:在開發工具編譯器中是同一個類型,因為參數List<T>擦除后是Object對象,所以參數相同。JDK編譯器 中不是同種方法,因為返回類型不同。
- 上面這段代碼是不能被編譯的,因為參數List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類型List<E>,擦除動作導致這兩種方法的特征簽名變得一模一樣。
- 由于Java泛型的引入,各種場景(虛擬機解析、反射等)下的方法調用都可能對原有的基礎產生影響和新的需求,如在泛型類中如何獲取傳入的參數化類型等。因此,JCP組織對虛擬機規范做出了相應的修改,引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來的參數類型的識別問題,Signature是其中最重要的一項屬性,它的作用就是存儲一個方法在字節碼層面的特征簽名,這個屬性中保存的參數類型并不是原生類型,而是包括了參數化類型的信息。修改后的虛擬機規范要求所有能識別49.0以上版本的Class文件的虛擬機都要能正確地識別Signature參數。
- 另外,從Signature屬性的出現我們還可以得出結論,擦除法所謂的擦除,僅僅是對方法的Code屬性中的字節碼進行擦除,實際上元數據中還是保留了泛型信息,這也是我們能通過反射手段取得參數化類型的根本依據。
八、泛型擦除后恢復實例
因為在編譯成CLASS是,在JVM有個
Signature
會對泛型弱記憶;然后可以Type
類中設置泛型類型,從而找到Signature 中記憶的泛型類型。
1)、導入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
* 有花括號{}:代表匿名內部類,創建一個匿名內部內實力對向
* 無花括號{}:創建實例對象
*/
Response<Date> date = gson.fromJson(str, new TypeRefrence<Response<Date>>(){}.getType());
System.out.println(date.toString());
3)、自定義gson的TypeToken類型
/**
* 當構造方法為protected, 或者 abstract class 創建時必須帶花括號{}
*
* @param <T>
*/
abstract class TypeRefrence<T> {
Type type;
protected TypeRefrence() {
//獲得泛型類型
Type genericSuperclass = getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//因為泛型類型可以定以多個A<T, v ...>所以是數組
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
type = actualTypeArguments[0];
}
public Type getType() {
return type;
}
}
4)、type類型子接口
-
TypeVariable
:泛型類型變量,可以泛型上下限等信息 -
ParameterizedType
:具體的泛型類型,可以獲取元數據中泛型簽名類型(泛型真實類型)。 -
GenericArrayType
:當需要描述的泛型是泛型類數組時,比如List[],Map[],此接口會作為Type的實現。 -
WildcardType
:通配符泛型,獲取上下限信息。