整體Retrofit內容如下:
- 1、Retrofit解析1之前哨站——理解RESTful
- 2、Retrofit解析2之使用簡介
- 3、Retrofit解析3之反射
- 4、Retrofit解析4之注解
- 5、Retrofit解析5之代理設計模式
- 6、Retrofit解析6之面向接口編程
- 7、Retrofit解析7之相關類解析
- 8、Retrofit解析8之核心解析——ServiceMethod及注解1
- 9、Retrofit解析8之核心解析——ServiceMethod及注解2
- 10、Retrofit解析9之流程解析
- 11、Retrofit解析10之感謝
本篇文章的主要內容如下:
- 1、什么是反射和反射機制
- 2、什么是Java反射
- 3、Java反射可以做什么
- 4、反射機制的優缺點
- 5、Java類加載原理
- 6、核心類及API
- 7、Method的invoke原理解析
- 8、泛型與反射
一、什么是反射與反射機制
(一)、什么是反射
反射主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力。在計算機科學領域,反射是一類應用,它們能夠自描述和自控制。這類應用通過某種機制來實現對自己行為的描述和檢測,并根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。
(二)、反射機制
反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法功能稱為反射機制
二、什么是Java反射
Java 反射是Java語言的一個很重要的特征,它使得Java具備了"動態化"。
在Java中的反射機制,被稱為Reflection。它允許運行中的Java程序對自身進行檢測,并能直接操作程序的內部屬性或方法。Reflection機制允許程序在正在執行的過程中,利用Reflection APIs取得任何已知名稱的類的內部信息。包括如下:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,并可以在執行過程中,動態生成Instances、變更fields內容或喚起methods。
通俗的講就是
.class反編譯-->.java,這樣就可以通過反射機制訪問Java對象的屬性,方法,構造方法等。
三、Java反射可以做什么?
Java反射機制主要提供以下功能:
- 運行時判斷任意一個對象所屬的類
- 在運行時構造任意個類的對象
- 在運行時判斷任意一個類所具有的成員變量和方法
- 在運行時調用人一個對象的方法
- 生成動態代理
四、反射機制的優缺點:
為什么要用反射機制,直接創建對象不就可以了嗎?這就涉及到了動態與靜態的概念。
- 靜態編譯:在編譯時確定類型,綁定對象,即通過。
- 動態編譯:運行時確定類型,綁定對象。動態編譯最大限度發揮了Java的靈活性,體現了多態的應用,有以降低類之間的耦合性。
優缺點
- 1、優點:
可以實現動態創建對象和編譯,體現出很大的靈活性,特別是J2EE的開發中它的靈活性就表現的十分明顯。比如,一個大型的軟件,不可能一次就把它設計的很完美,當這個程序編譯,發布了,當發現需要更新某些功能時,我們不可能要用戶把之前的卸載,再重新安裝新的版本,假如這樣的話,這個軟件肯定是沒有多少人用的。采用靜態的話,需要把整個程序重新編譯一次才可以實現功能的更新,而采用反射機制的話,它就可以不用卸載,只需要在運行時都才動態的創建和編譯,就可以顯示該功能。 - 2、缺點:
對性能有影響,使用反射基本上是一種解釋操作,我可以告訴JVM,我希望做什么并且它滿足我們的需求,這類操作總是慢于直接執行相同的操作。
五、Java類加載原理
為了后里面講解Java反射原理方便,在這里先講解Java類加載原理
(一)、概述
Class文件由類裝載器加載后,在JVM中將形成一份描述Class結構的元數據對象,通過該元數據可以獲知Class的結構信息:如構造函數,屬性和方法等,Java允許用戶借這個Class相關原信息對象間接調用Class對象的功能。
虛擬機把描述類的數據從class文件加載到內存,并對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
JVM把class文件加載到內存,并對數據進行校驗,解析和初始化,最形成JVM可以直接使用的JAVA類型的過程。
加載-->鏈接(-->驗證-->準備-->解析)-->初始化-->使用-->卸載
1、加載
將class文件字節碼內容加載到內存中,并將這些靜態數據轉換成方法區中運行時數據結構,在堆中生成一個代表這個類的java.lang.Class對象,作為方法區類數據的訪問入口。
將class文件字節碼內容加載到內存中,并將這些靜態數據轉換成方法區中的運行時數據結構,在堆中生成一個代表這個類的java.lang.Class對象,作為方法區類數據的訪問入口。
class字節碼-->類加載類--->內存(Class對象,方法區中運行時數據)--->外部可以通過Class對象,操作類
2、鏈接
將Java類的二進制代碼合并到JVM的運行狀態之中的過程
- 驗證:確保加載的類信息符合JVM規范,沒有安全方面的問題。
- 準備:正式為類變量(static變量)分配內存并設置變量初始值的階段,這些內存都將在方法區中進行分配
- 解析:虛擬機常量池內的符合引用替換為直接引用的過程。
- 常用吃:虛擬常用池內的符合引用替換為直接引用的過程。
3、初始化
- 初始化階段是執行類構造器Class的<clinit>()方法的過程。類構造器<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句(static)塊中的語句合并產生的。
- 當初始化一個類的時候,如果發現其弗雷還沒有進行初始化、則需要先執行父類的初始化。
- 虛擬機會保證一個的類<clinit>()方法在多線程中唄正確的加鎖和同步。
- 當訪問一個Java類的靜態域時,只有真正聲明這個域才會被初始化。
4、使用
5、卸載
使用和卸載沒有什么好講解的,就不說了。
最后附上 別人的思維導圖
(二) 其它:
1、這里說下<clinit>和<init>的區別
在編譯生成class文件時,會自動產生兩個方法,一個是類的初始化方法<clinit>,另一個是實例化的初始化方法<init>
- <clinit>:在JVM第一次加載class文件時調用,包括靜態變量初始化語句和靜態塊的執行
- <init>: 在實例創建出來的時候調用,包括調用new操作符;滴啊用Class或者java.lang.reflect.Constructor對象的newInstance()方法;調用任何現有對象的clone()方法;通過java.io.ObjectInputStream類的getObject()方法反序列化。
2、主動引用和被動引用
主動引用:一定會發生類的初始化
- new一個類的對象
- 調用類的靜態成員(除了final常量)和靜態方法
- 使用java.lang.reflect包的方法對類進行反射調用
- 當虛擬機啟動,java helloworld,則一定會初始化helloworld類,說白了就是先啟動main方法所在的類。
- 當初始化一個類,如果其父類沒有被初始化,則會先初始化他的父類。
被動引用:不會發生類的初始化
- 當訪問一個靜態域時,
當訪問一個靜態域時,只有真正聲明這個域的類才會被初始化
通過子類引用父類的靜態變量,不會導致子類初始化
通過數組定義類引用,不會觸發此類的初始化
引用常量不會觸發此類的初始化(常量在編譯階段就存入調用類的常量池中了)
六、核心類及API
核心類,位于java.lang.reflect包中
- Class類:代表一個類
- Field類:代表一個類
- Method類:代表類的方法
- Constructor類:代表類的構造方式
- Array類:提供了動態創建數組,以及訪問數組的元素的靜態方法
(一)Class
1、Class是什么?
Java程序在運行時,Java運行時系統一直對所有的對象進行所謂的運行時類型標記,這項信息記錄了每個對象所屬的類。虛擬機通常使用運行時類型信息選擇正確方法去執行,用來保存這些類型的類是Class類。
Class類封裝一個對象和接口運行時的狀態,當加載類時,Class類型的對象自動創建。Class沒有公共構造方法。Class對象是在加載類時由Java虛擬機以及通過調用類加載器中的defineClass方法自動構造的,因此不能顯示地聲明一個Class對對象。
虛擬機為每種類型管理一個獨一無二的Class對象。也就是說,每個類(型)都有一個Class對象。運行程序時,Java虛擬機(JVM)首先檢查是否要加載的類對應的Class對象是否已經加載。如果沒有加載,JVM就會根據類名查找.class文件,并將其Class對象加載。
基本的Java類型(boolean、byte、char、shor、int、long、float、double)和關鍵字void也都對應一個Class對象。每個數據屬于被映射為Class對象的一個類,所有具有相同元素類型和維數的數組都
共享該Class對象。一般某個類Class對象被載入內存,它就用來創建這個類的所有對象。
2、Class類的常用方法
在java.lang.Object類中定義了getClass()方法,因此對于任意一個Java對象,都可以通過此方法獲得對象的類型。Class類是Reflection API中的核心類。它有如下方法:
方法名 | 作用 |
---|---|
getName() | 獲得類的完整名稱 |
getFields() | 獲得類的public類型的屬性 |
getDeclaredFields() | 獲得類的所有屬性 |
getMethod() | 獲得類的public類型方法 |
getDeclaredFiedMethods() | 獲得類的所有方法 |
getMethod(String name,Class[] parameterTypes) | 獲得類的特定方法,name參數制定方法的名字,parameterTypes 參數制定方法參數類型 |
getConstructors() | 獲得類的public類型的構造方法 |
getConstructor(Class[] parameterTypes) | 獲得類的特定構造方式,parameterTypes 參數指定構造方法的參數類型 |
newInstance() | 通過類的不帶參數的構造方法創建這個類的一個對象 |
PS:
- 大家在使用Class實例化其他類的對象的時候,一定要自己定義午餐的構造函數
- 所有類的對象其實都得Class的實例
3、獲取Class對象的三種方式
- 通過Object類的getClass()方法
例如 Class c1=new String("hello world").getClass(); - 通過Class類的靜態方法——forName()來實現:
Class c2 = Class.forName("MyObject"); - 如果T是一個已定義的類型的話,在java中,它的.class文件名:T.class就代表了與其匹配的Class對象。例如:
Class c3 = Manager.class;
Class c4 = int.class;
Class c5 = Double[].class;
(二)、Constructor
Constructor 提供關于類的單個構造方法的信息以及對它的訪問權限,用來封裝反射得到的構造器。
1、獲取構造方法
Class 類提供四個public方法,用于獲取某個類的構造方法
方法名 | 作用 |
---|---|
Constructor getConstructor(Class[] params) | 根據構造函數的參數,返回一個具體的具有public屬性的構造函數 |
Constructor[] getConstructor() | 返回 所有具有public屬性的構造函數數組 |
Constructor getDeclaredConstructor(Class[] params) | 根據構造函數的參數,返回一個具體的構造函數(不分public和非public 屬性) |
Constructor getDeclaredConstrutors() | 返回該類中所有的構造函數數組(不分public 和非public屬性) |
由于Java語言是一種面向對象的語言,具有多態的性質,那么我們可以通過構造方法的參數列表的不同,來調用不同的構造方法去創建類的實例。同樣,獲取不同的構造方法的信息,也需要提供與之對應的參數類型信息;因此,就產生了以上四種不同的獲取構造方法的方式。
(三)、Field 成員變量信息
想一想成員變量中都包含什么:
成員變量類型+成員變量名
類的成員變量也是一個對象,它是java.lang.reflect.Field的一個對象,所以我們通過java.lang.reflect.Field里面封裝的方法來獲取這些信息。
如果想單獨獲取某個成員變量,通過Class類的以下方法實現:
方法名 | 作用 |
---|---|
Field getDeclaredField(String name) | 獲得該類自身聲明的所有變量,不包括其父類的變量 |
Field getField(String name) | 獲得該類自所有的public成員變量,包括其父類變量 |
舉例如下:
參數是成員變量的名字。
例如一個類A有如下成員變量:
private int n;
如果A有一個對象a,那么就可以這樣得到其成員變量:
Class c = a.getClass();
Field field = c.getDeclaredField("n");
由于Method 在Retrofit比較重要,我們就單獨講解以下
(四) Method類及invoke
Method 提供關于類或接口上單獨某個方法(以及如何反問該方法)的信息,一個完整方法包含的屬性有:方法上使用的注解、方法的修飾符、方法上定義的泛型參數、方法的返回值、方法名稱、方法拋出的異常。
1、獲取Method
有4種獲取Method的方式:
方法名 | 作用 |
---|---|
Method getMethod(String name, Class[] params) | 根據方法名和參數,返回一個具體的具有public屬性的方法 |
Method[] getMethods() | 返回所有具有public屬性的方法數組 |
Method getDeclaredMethod(String name, Class[] params) | 根據方法名和參數,返回一個具體的方法(不分public和非public屬性) |
Method[] getDeclaredMethods() | 返回該類中的所有的方法數組(不分public和非public屬性) |
PS:在獲取類的方法時,有一個地方值得大家注意,就是getMethods()方法和getDeclaredMethods()方法。
在
- getMethods():用于獲取類的所有的public修飾域的成員方法,包括從父類繼承的public方法和實現接口的public方法
- getDeclaredMethods():用于獲取當前類中定義的所有的成員方法和實現接口的方法,不包括從父類繼承的方法。
2、Method的API
那來看下Method的API,因為Retrofit里面大量用到了Method的api
方法名 | 作用 |
---|---|
<T extends Annotation> T getAnnotation(Class<T> annotationClass) | 如果存在該元素的指定類型的注釋,則返回這些注解,否則返回 null |
Annotation[] etDeclaredAnnotations() | 返回直接存在于此元素上的所有注解 |
Class<?> getDeclaringClass() | 返回表示聲明由此 Method 對象表示的方法的類或接口的 Class 對象 |
Object getDefaultValue() | 返回由此 Method 實例表示的注解成員的默認值。 |
Class<?>[] getExceptionTypes() | 返回 Class 對象的數組,這些對象描述了聲明將此 Method 對象表示的底層方法拋出的異常類型。 |
Type[] getGenericExceptionTypes() | 返回 Type 對象數組,這些對象描述了聲明由此 Method 對象拋出的異常。 |
Type[] getGenericParameterTypes() | 按照聲明順序返回 Type 對象的數組,這些對象描述了此 Method 對象所表示的方法的形參類型的。 |
Type getGenericReturnType() | 返回表示由此 Method 對象所表示方法的正式返回類型的 Type 對象 |
int getModifiers() | 以整數形式返回此 Method 對象所表示方法的 Java 語言修飾符。 |
String getName() | 以 String 形式返回此 Method 對象表示的方法名稱。 |
Annotation[][] getParameterAnnotations() | 返回表示按照聲明順序對此 Method 對象所表示方法的形參進行注釋的那個數組的數組。 |
Class<?>[] getParameterTypes() | 按照聲明順序返回 Class 對象的數組,這些對象描述了此 Method 對象所表示的方法的形參類型。 |
Class<?> getReturnType() | 返回一個 Class 對象,該對象描述了此 Method 對象所表示的方法的正式返回類型。 |
TypeVariable<Method>[] getTypeParameters() | 返回 TypeVariable 對象的數組,這些對象描述了由 GenericDeclaration 對象表示的一般聲明按聲明順序來聲明的類型變量 |
Object invoke(Object obj, Object... args) | 對帶有指定參數的指定對象調用由此 Method 對象表示的底層方法。 |
補充一個知識點:
在反射機制中,Field的getModifiers()方法返回int類型值表示該字段的修飾符,其中該修飾符是java.lang.reflect.Modifier的靜態屬性。對應如下:
Modifier.java
/**
* The {@code int} value representing the {@code public}
* modifier.
*/
public static final int PUBLIC = 0x00000001;
/**
* The {@code int} value representing the {@code private}
* modifier.
*/
public static final int PRIVATE = 0x00000002;
/**
* The {@code int} value representing the {@code protected}
* modifier.
*/
public static final int PROTECTED = 0x00000004;
/**
* The {@code int} value representing the {@code static}
* modifier.
*/
public static final int STATIC = 0x00000008;
/**
* The {@code int} value representing the {@code final}
* modifier.
*/
public static final int FINAL = 0x00000010;
/**
* The {@code int} value representing the {@code synchronized}
* modifier.
*/
public static final int SYNCHRONIZED = 0x00000020;
/**
* The {@code int} value representing the {@code volatile}
* modifier.
*/
public static final int VOLATILE = 0x00000040;
/**
* The {@code int} value representing the {@code transient}
* modifier.
*/
public static final int TRANSIENT = 0x00000080;
/**
* The {@code int} value representing the {@code native}
* modifier.
*/
public static final int NATIVE = 0x00000100;
/**
* The {@code int} value representing the {@code interface}
* modifier.
*/
public static final int INTERFACE = 0x00000200;
/**
* The {@code int} value representing the {@code abstract}
* modifier.
*/
public static final int ABSTRACT = 0x00000400;
/**
* The {@code int} value representing the {@code strictfp}
* modifier.
*/
public static final int STRICT = 0x00000800;
簡單一點就是:
- PUBLIC: 1
- PRIVATE: 2
- PROTECTED: 4
- STATIC: 8
- FINAL: 16
- SYNCHRONIZED: 32
- VOLATILE: 64
- TRANSIENT: 128
- NATIVE: 256
- INTERFACE: 512
- ABSTRACT: 1024
- STRICT: 2048
3、Method的invoke方法
注意事項1
通過Method對象調用invoke()方法
//獲取一個方法名為doHello,參數類型為String的方法
Method method = MyObject.class.getMethod("doHello", String.class);
Object returnValue = method.invoke(null, "value1");
如果是一個靜態方法調用的話,可以穿用null代替制定對象作為invoke()方法的參數,在上面的方法中,如果doHello不是靜態方法的話,你就要傳入有效的MyObject對象,而不是null。
注意事項2
當通過Method的invoke()方法來調用對應的方法時,Java會要求程序必須有調用該方法的權限,如果程序確實需要調用某個對象的private方法,則可以先調用Method對象的如下方法。
setAccessible(boolean flag)
將Method對象的accessible設置為制定的布爾值。值為true,指示Method在使用時應該取消Java語言訪問權限檢查;值為false,則指示該Method在使用時實施Java語言的訪問權限檢查。
七、Method的invoke原理解析
先上代碼
Class actionClass=Class.forName(“MyClass”);
Object action=actionClass.newInstance();
Method method = actionClass.getMethod(“myMethod”,null);
method.invoke(action,null);
上面就是最常見的反射使用的例子,前兩行實現了類的裝載、鏈接和初始化(newInstance方法實際上也是使用反射調用了<init>方法),后兩行實現了從class對象中獲取到method對象然后執行反射調用。下面簡單分析一下后兩行的原理。
Class Method{
public Object invoke(Object obj,Object[] param){
MyClass myClass=(MyClass)obj;
return myClass.myMethod();
}
}
method.invoke(action,null);的原理其實就是動態的生成類似于上面的字節碼,加載到JVM中運行。
1、獲取Method對象
首先來看下Method對象是如何生成的。
上面的Class對象是在加載類時由JVM構造的,JVM為每個類管理一個獨一無二的Class對象,這份Class對象里面維護著該類的所有Method,Field,Constructor的cache,這份cache也可以被稱為根對象。每次getMethod獲取到的Method對象都持有對跟對象的引用,因此一些重量級別的Method的成員變量(主要是MethodAccessor),我們不希望每次創建Method都要重新初始化,于是所有代表同一個方法的Method對象都共享根對象的MethodAccessor,每一次創建都會調用對象的copy方法復制一份:
Method copy() {
if(this.root != null) {
throw new IllegalArgumentException("Can not copy a non-root Method");
} else {
Method var1 = new Method(this.clazz, this.name, this.parameterTypes, this.returnType, this.exceptionTypes, this.modifiers, this.slot, this.signature, this.annotations, this.parameterAnnotations, this.annotationDefault);
var1.root = this;
var1.methodAccessor = this.methodAccessor;
return var1;
}
}
2、調用invoke()方法
獲取到Method對象之后,調用invoke方法的流程如下:
上代碼
@CallerSensitive
public Object invoke(Object var1, Object... var2) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if(!this.override && !Reflection.quickCheckMemberAccess(this.clazz, this.modifiers)) {
Class var3 = Reflection.getCallerClass();
this.checkAccess(var3, this.clazz, var1, this.modifiers);
}
MethodAccessor var4 = this.methodAccessor;
if(var4 == null) {
var4 = this.acquireMethodAccessor();
}
return var4.invoke(var1, var2);
}
代碼解釋
- 首先,檢查AccessibleObject的overrider屬性是否為true。因為AccessibleObject是Method、Field、Constructor的父類,override屬性默認為false,可調用setAccessible方法改變,如果設置為true,則表示可以忽略訪問權限的限制,直接調用。
- 其次,如果不是true,則要進行訪問權限監測,用Reflection的quickCheckMemberAccess方法檢查是不是public,如果不再用Reflection.getCallerClass()方法獲得到調用這個方法的Class,然后做是否有權限訪問的校驗,校驗之后緩存一次,以便下次如果還是這個類調用就不用去做校驗,直接用上次的結果。(很奇怪用這種方式緩存,因為這種方式如果下次換個類來調用的話,就不會用緩存了,而再驗證一遍,把這次的結果作為緩存,但上一次的緩存結果就沖掉了,這是一個很簡單的緩存機制,只適用一個類的重復調用。)
- 再次,調用MethodAccessor的invoke()方法。每個Method對象包含一個root對象,root對象里持有
一個MethodAccessor對象。我么獲得的Method獨享相當于一個root對象的鏡像,所有這類Method共享root李的MethodAccessor對象,(這個對象有ReflectionFactory方法生成,ReflectionFactory對象在Method類中是static final的native方法實例化。) - 最后,我們深入一下NativeMethodAccessorImpl的invoke()方法,NativeMethodAccessorImpl的invoke方法里面調用了native方法的invoke執行。可以都看到,調用Method.invoke之后,就會去調用MethodAccessor.invoke().MethodAccessor就是上面提到的所有同名method共享的對象,有ReflectionFactory創建。創建機制采用了一種名為inflation的方式,如果該方法的累計調用次數<=15,會創建出NativeMethodAccessorImpl,它的實現就是直接調用native方法實現反射;如果該方法的累計調用次數>15,會有Java代碼創建處于字節碼組裝而成的MethodAccessorImpl。
PS:是否采用inflation和15這個數字都可以在JVM參數中調整。
總結一下:
一個方法可以生成多個Method對象,但只有一個root對象,主要用持有一個MethodAccessor對象,這個對象也可以認為一個方法只有一個,相當于是static的,因為Method的invoke是交給MethodAccessor執行的,
八、泛型與反射
(一)、什么是泛型
泛型(Generic type 或者 generics) 是對Java語言的類型系統的一種擴展,以支持創建可以按類型進行參數化的類。可以把來類型參數看做是使用參數化類型時制定的類型的一個占位符,就像方法的形式參數是運行時傳遞值的占位符一樣。
可以在集合框架( Collection frameword ) 中看到泛型的動機。例如,Mapl類允許您向一個Map 添加任意類的對象,即使最常見的情況是在給定映射(map)中保存某個特定類型(比如 String) 的對象。因為Map.get()被定義為返回Object,所以一般必須將Map.get()的結果強制類型轉換為期望的類型,如下面的代碼所示:
Map m = new HashMap();
m.put("key", "hello");
String s = (String) m.get("key");
要讓程序通過編譯,必須將get()的結果強制類型轉換為String,并且希望結果真的是一個String。但是有可能某人已經在映射中保存了不是String的東西,這樣的話,上面的代碼將會拋出ClassCastException。
理想情況下,你可能會得出這樣一個觀點,即m是一個Map,它將String鍵映射到String值。這可以讓您消除代碼中強制類型轉換,同時獲得一個附加的類型檢查層,該檢查層可以防止有人將錯誤類型的鍵或值保存在集合中。這樣就是泛型所做的工作。
(二)、泛型的好處
Java語言中引入泛型是一個比較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了大翻修,所以許多重要的類,比如集合框架,已經成為泛型化,這帶來很多好處:
- 類型安全:泛型的主要目標是提高Java程序的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程序上驗證類型假設。沒有泛型,這些假設就只存在于程序員的頭腦中(或者如果幸運的話,還存在注釋中)。
Java程序中的一種流行技術是定義這樣的集合,即它的元素或鍵是公共類型的,比如"String 列表"或者"String 到 String 的映射"。通過在變量聲明中捕獲這一附加的類型信息,泛型允許編譯器實施這些附加的類型約束。類型錯誤現在就可以在編譯時被捕獲了,而不是在運行時當做ClassCastException 展示出來。將類型檢查從運行時挪到編譯時有助于你更容易找到錯誤。并提高程序的可靠性。 - 消除強制類型轉換。泛型的一個附帶好處是,消除源代碼中的許多強制類型轉化。這使得代碼更加可讀,并且減少了出錯的機會
(三)、命名類型參數
推薦的命名約定是使用大寫的單個字幕作為類型參數。這與C++約定有所不同,并反映了大多數泛型類將具有少量類型參數的假設。對常見的泛型模式,推薦的名稱是:
- K————鍵,比如映射的鍵
- V————值,比如List和Set的內容,或者Map中的值。
- E————異常類
- T————泛型
(四)泛型擦除
1、類型擦除
正確理解泛型概念的首要前提是理解類型擦除(type erasure)。Java中泛型基本上都是編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就叫做類型擦除。
比如在代碼中定義List<Object>和List<String>等類型,在編譯之后就會變成List。JVM看到的是List,而泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時盡可能的發現可能出錯的地方,但是仍無法避免在運行時出現類型轉換異常的情況。類型擦除也是Java泛型實現方法與C++模板機制實現方法是之間的重要區別
注意:
很多泛型的奇怪特性都與這個類型擦除的存在有關,包括泛型類并沒有自己獨有的Class類對象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。靜態變量是被泛型類的所有對象所共享的。對于聲明為MyClass<T>的類,訪問其中的靜態變量的方法仍然是MyClass.myStaticVar。不管是通過new MyClass<String> 還是new MyClass<Integer> 創建的對象,都是共享一個靜態變量。泛型的類型參數不能用在Java異常處理的catch語句中,因為異常處理是由JVM在運行時進行的,由于類型信息被擦除,JVM是無法區分兩個異常類型MyExcetption<String>和MyExcetion<Integer>的。對于JVM來說,他們都是MyExcetpion類型。也就是無法執行與異常對應的catch語句
(五)、通配符與上下界
在使用泛型類的時候,既可以指定一個具體的類型,比如List<String> 就聲明了具體的類型是String,也可以使用通配符?來表示位置類型,比如List<?>就聲明了List中包含的元素類型是未知的。通配符所代表的其實是一組類型,但具體的類型是未知的。List<?> 所聲明的就是所有類型都是可以的。但是List<?>并不等同與List<Object>。List<Object>實際上確定了List中包含的是Object及其子類,在使用的時候都可以通過Object來進行引用。而List<?>則表示其中所包含的類型是不確定的。其中可能包含的是String,也可能是Integer。如果它包含了String的話,往里面添加Integer類型的元素就是錯誤的。正因為類型未知,就不能通過new ArrayList<?>()來創建一個新的ArrayList對象,因為編譯器無法知道具體的類型是什么。但是對于List<?>中的元素卻總是可以通過Object來引用。因為雖然類型未知,但肯定是Object及其子類。
(六) 泛型的使用注意事項:
在使用泛型的時候可以遵循一些基本的原則,從而避免一些常見的問題。
- 在代碼中避免泛型類和原始類型的混用。比如List<String> 和List不應該共同使用。這樣會產生一些編譯器警告和潛在的運行時異常。
- 在使用帶通配符的泛型類的時候,需要明確通配符所代表的一組類型的概念。由于具體的類型是未知的,很多操作是不允許的。
- 泛型最好不要和同數組一塊使用。
- 不要忽視編譯器給出的警告信息。
(七) 泛型與反射
1、Java泛型與反射結構
java中class,method,field的繼承體系
java中所有對象的類型定義類Type
2、相關類說明
(1)、Type
它 是所有類型的公共接口,包括:
- 原始類(raw types[對應 Class])
- 參數化類型(parameterized types [對應 ParameterizedType])
- 數組類型(array types [ 對應 GenericArrayType])
- 類型變量(type variables [對應 TypeVariable ])
- 基本類型((primitivetypes)[對應 Class ])
同時ParameterizedType,TypeVariable,WildcardType,GenericArrayType這四個接口都是它的子接口。
(2)、GenericDeclaration 接口
1、接口含義:所有可以聲明類型變量(TypeVariable)的公共父接口
2、直接實現子類:java.lang.reflect子包中的:這個接口Class、Method、Constructor都有實現
注意:方法的屬性上面不能定義類型變量,所以GenericDeclaration的直接實現子類沒有Field類
GenericDeclararion接口的源碼
package java.lang.reflect;
public interface GenericDeclaration {
public TypeVariable<?>[] getTypeParameters();
}
方法的返回值類型是數組,原因就是一個可以聲明類型變量的實體可以同時聲明多個類型變量,并且每一個元素類型是TypeVariable類型。
(3)、 TypeVariable接口
它表示類型變量。
package java.lang.reflect;
public interface TypeVariable<D extends GenericDeclaration> extends Type {
/**
* Returns the upper bounds of this type variable. {@code Object} is the
* implicit upper bound if no other bounds are declared.
*
* @return the upper bounds of this type variable
*
* @throws TypeNotPresentException
* if any of the bounds points to a missing type
* @throws MalformedParameterizedTypeException
* if any of the bounds points to a type that cannot be
* instantiated for some reason
*/
Type[] getBounds();
/**
* Returns the language construct that declares this type variable.
*
* @return the generic declaration
*/
D getGenericDeclaration();
/**
* Returns the name of this type variable as it is specified in source
* code.
*
* @return the name of this type variable
*/
String getName();
}
泛型接口TypeVariable<D extends GenericDeclaration> 可以知道TypeVariable中通過泛型限定extends指定的父類正好的是接口GenericDeclaration。而GenericDeclaration的直接實現子類僅有Class,Method和Constructor,所以TypeVariable的所有參數化類型是
- TypeVariable<Class>
- TypeVariable<Method>
- TypeVariable<Constructor>
由于Class和Constructor本身也是泛型類,但是Method本身不是泛型,所以上面的參數化類型應該是如下:
- TypeVariable<Class<T>> :Class<T>類上聲明的TypeVariable
- TypeVariable<Method> : Method類上聲明的TypeVariable
- TypeVariable<Constructor<T>> : Constructor類上聲明的TypeVariable
所以TypeVariable中的泛型是對定義TypeVariable位置的描述,不同的GenericDeclaration的實現子類代表了這個類型變量到底是在類上定義的,還是方法上定義的,還是在構造上定義的。
現在來分析下三個方法
先定義public class TypeVariableBean<K extends InputStream & Serializable, V> ,其中K ,V 都是屬于類型變量。
- Type[] getBounds():得到上邊界的Type數組,如K的上邊接數組是InputStream和Serializable。V沒有指定則是Object。
- D getGenericDeclaration(): 返回的是聲明這個Type所在類的Type
- String getName() : 返回的是這個 type variable的名稱
(4)、ParameterizedType
它標識參數化類型,源碼如下:
/**
* This interface represents a parameterized type such as {@code
* 'Set<String>'}.
*
* @since 1.5
*/
public interface ParameterizedType extends Type {
/**
* Returns an array of the actual type arguments for this type.
* <p>
* If this type models a non parameterized type nested within a
* parameterized type, this method returns a zero length array. The generic
* type of the following {@code field} declaration is an example for a
* parameterized type without type arguments.
*
* <pre>
* A<String>.B field;
*
* class A<T> {
* class B {
* }
* }</pre>
*
*
* @return the actual type arguments
*
* @throws TypeNotPresentException
* if one of the type arguments cannot be found
* @throws MalformedParameterizedTypeException
* if one of the type arguments cannot be instantiated for some
* reason
*/
//返回 這個 Type 類型的參數的實際類型數組。
// 如 Map<String,Person> map
//這個 ParameterizedType 返回的是 String 類,
//Person 類的全限定類名的 Type Array。
Type[] getActualTypeArguments();
/**
* Returns the parent / owner type, if this type is an inner type, otherwise
* {@code null} is returned if this is a top-level type.
*
* @return the owner type or {@code null} if this is a top-level type
*
* @throws TypeNotPresentException
* if one of the type arguments cannot be found
* @throws MalformedParameterizedTypeException
* if the owner type cannot be instantiated for some reason
*/
Type getOwnerType();
/**
* Returns the declaring type of this parameterized type.
* <p>
* The raw type of {@code Set<String> field;} is {@code Set}.
*
* @return the raw type of this parameterized type
*/
//返回的是當前這個 ParameterizedType 的類型。
//如 Map<String,Person> map
//這個 ParameterizedType 返回的是 Map 類的全限定類名的 Type Array。
Type getRawType();
}
官方給的解釋是
ParameterizedType represents a parameterized type such as Collection<String>
需要注意的是,并不只是 Collection<String> 才是 parameterized,任何類似于 ClassName<V> 這樣的類型都是 ParameterizedType 。比如下面的這些都是
- Map<String, Person> map;
- Set<String> set1;
- Class<?> clz;
- Holder<String> holder;
- List<String> list;
- static class Holder<V>{}
而類似于這樣的 ClassName 不是 ParameterizedType.
Set mSet;
List mList;
(5)GenericArrayType
/**
* {@code GenericArrayType} represents an array type whose component
* type is either a parameterized type or a type variable.
* @since 1.5
*/
public interface GenericArrayType extends Type {
/**
* Returns a {@code Type} object representing the component type
* of this array. This method creates the component type of the
* array. See the declaration of {@link
* java.lang.reflect.ParameterizedType ParameterizedType} for the
* semantics of the creation process for parameterized types and
* see {@link java.lang.reflect.TypeVariable TypeVariable} for the
* creation process for type variables.
*
* @return a {@code Type} object representing the component type
* of this array
* @throws TypeNotPresentException if the underlying array type's
* component type refers to a non-existent type declaration
* @throws MalformedParameterizedTypeException if the
* underlying array type's component type refers to a
* parameterized type that cannot be instantiated for any reason
*/
Type getGenericComponentType();
}
簡單的來說就是:泛型數組,組成數組的元素中有泛型則實現了該接口;它的組成元素是ParameterizedType或者TypeVariable類型
// 屬于 GenericArrayType
List<String>[] pTypeArray;
// 屬于 GenericArrayType
T[] vTypeArray;
// 不屬于 GenericArrayType
List<String> list;
// 不屬于 GenericArrayType
String[] strings;
// 不屬于 GenericArrayType
Person[] ints;
下面我們一起來看一下例子
public class GenericArrayTypeBean<T> {
public void test(List<String>[] pTypeArray, T[] vTypeArray,
List<String> list, String[] strings, Person[] ints) {
}
}
public static void testGenericArrayType() {
Method method = GenericArrayTypeBean.class.getDeclaredMethods()[0];
System.out.println(method);
// public void test(List<String>[] pTypeArray, T[]
// vTypeArray,List<String> list, String[] strings, Person[] ints)
Type[] types = method.getGenericParameterTypes(); // 這是 Method 中的方法
for (Type type : types) {
System.out.println(type instanceof GenericArrayType);// 依次輸出true,true,false,false,false
}
}
輸出結果
public void com.demo.beans.GenericArrayTypeBean.test(java.util.List[],java.lang.Object[],java.util.List,java.lang.String[],com.demo.beans.Person[])
true
true
false
false
false
(6)、WildcardType 通配符的類型
/**
* Represents a wildcard type argument.
* Examples include: <pre>
* {@code <?>}
* {@code <? extends E>}
* {@code <? super T>}
* </pre>
* A wildcard type can have explicit <i>extends</i> bounds
* or explicit <i>super</i> bounds or neither, but not both.
*
* @author Scott Seligman
* @since 1.5
*/
public interface WildcardType extends Type {
/**
* Return the upper bounds of this wildcard type argument
* as given by the <i>extends</i> clause.
* Return an empty array if no such bounds are explicitly given.
*
* @return the extends bounds of this wildcard type argument
*/
Type[] extendsBounds();
/**
* Return the lower bounds of this wildcard type argument
* as given by the <i>super</i> clause.
* Return an empty array if no such bounds are explicitly given.
*
* @return the super bounds of this wildcard type argument
*/
看類注釋知道
{@code ?}, {@code ? extends Number}, or {@code ? super Integer} 這些類型 都屬于 WildcardType
PS:extends 用于指定上邊界,沒有指定的話上邊界默認是Object,super用來指定下邊界,沒有指定的話為null
主要方法解釋:
- Type[] getLowerBounds() 得到上邊界 Type 的數組
- Type[] getUpperBounds() 得到下邊界 Type 的數組
(7)、Type總結
Type及其子接口的來歷
泛型出現之前的類型
沒有泛型的時候,只有原始類型。此時,所有原始類型都通過字節碼文件類Class進行抽象。Class類的一個具體對象就代表一個指定的原始類型。
泛型出現之后的類型
泛型出現之后,擴充了數據類型。從只有原始類型擴充了參數畫類型、類型變量類型、限定符類型、泛型數組類型。
2、Generic Method Return Type(方法返回值類型的泛型)
如果你獲得了java.lang.reflect.Method對象,你也是有可能獲得它的返回值類型的泛型信息的。這不會是任何參數化類型的Method對象,除了在類里面使用了參數化類型。舉一個類有參數化返回值類型的返回值:
public class MyClass {
protected List<String> stringList = new ArrayList<String>();
public List<String> getStringList(){
return this.stringList;
}
}
在這種情況下,可以取得getStringList()方法的泛型返回值類型。換句話說,是可以檢測到getStringList()方法返回的是List<String> 類型而不僅僅是List。代碼如下:
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
這段代碼將會打印出“typeArgClass = java.lang.String”.Type[ ]類型的數組typeArguements中包含一個項,一個代表實現了Type接口的java.lang.String.Class的Class實例。
2、Generic Method Parameter Types
在運行時,你也可以通過Java反射機制訪問泛型參數的類型,下面舉例說明
public class MyClass {
protected List<String> stringList = new ArrayList<String>();
public void setStringList(List<String> list){
this.stringList = list;
}
}
你可以像這樣來訪問其方法參數的參數化類型:
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
這段代碼將會打印出“parameterArgType = java.lang.String”。Type[ ]類型的數組parameterArgTypes中包含一個項,一個代表實現了Type接口的java.lang.String.Class的Class實例。
至此反射講解完畢,最后奉上別人做的比較不錯的思維導圖