什么是反射
反射是一種計(jì)算機(jī)處理方式诀豁。有程序可以訪問(wèn)、檢測(cè)和修改它本身狀態(tài)或行為的這種能力阱驾。能提供封裝程序集就谜、類型的對(duì)象。
對(duì)于Java這種OOP語(yǔ)言來(lái)講,運(yùn)行狀態(tài)中里覆,我們可以根據(jù)“類的部分信息”來(lái)還原“類的全部信息”,這就是Java中的反射吁伺。
Java虛擬機(jī)的體系結(jié)構(gòu)
Java虛擬機(jī)屏蔽了與具體操作系統(tǒng)平臺(tái)相關(guān)的信息,使得Java程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼)租谈,就可以在多種平臺(tái)上不加修改地運(yùn)行篮奄。通俗地說(shuō)Java虛擬機(jī)就是處理Java程序(確切地說(shuō)是Java字節(jié)碼)的虛擬機(jī)。
作為虛擬機(jī)割去,JVM的結(jié)構(gòu)和常見(jiàn)的操作系統(tǒng)一致窟却,有著自己的堆、棧呻逆、方法區(qū)夸赫、PC計(jì)數(shù)器和指令系統(tǒng)。它的結(jié)構(gòu)如下圖所示:
這里我們暫時(shí)不去談?lì)惣虞d子系統(tǒng)與執(zhí)行引擎咖城,只談一下Java運(yùn)行時(shí)的數(shù)據(jù)區(qū)茬腿,它由五個(gè)部分組成:
(1)程序計(jì)數(shù)器(線程私有)
程序計(jì)數(shù)器是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器呼奢,字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令,
分支切平、循環(huán)握础、跳轉(zhuǎn)、異常處理悴品、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來(lái)完成禀综。
在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對(duì)于多核處理器來(lái)說(shuō)是一個(gè)內(nèi)核)只會(huì)執(zhí)行一條線程中的指令苔严。
因此定枷,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的線程計(jì)數(shù)器届氢,各條線程之間的計(jì)數(shù)器互不影響欠窒,獨(dú)立存儲(chǔ)尊蚁。
(2)虛擬機(jī)棧(線程私有)
在Java(或者其他JVM的語(yǔ)言)每個(gè)方法被執(zhí)行的時(shí)候都會(huì)同時(shí)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表燕雁、操作數(shù)棧、動(dòng)態(tài)鏈接抬探、方法出口等信息絮供。
每一個(gè)方法被調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程茶敏。
Java虛擬機(jī)棧存放局部變量表壤靶,如編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte惊搏、char贮乳、short、int恬惯、float向拆、long、double)酪耳、對(duì)象引用(reference類型浓恳,它不等同于對(duì)象本身,根據(jù)不同的虛擬機(jī)實(shí)現(xiàn)碗暗,它可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔樉苯部赡苤赶蛞粋€(gè)代表對(duì)象的句柄或者與此對(duì)象相關(guān)的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。
這也導(dǎo)致在Java中匿名內(nèi)部類來(lái)自外部閉包環(huán)境的自由變量必須是final的(Java編譯器是capture-by-value模式)言疗,不過(guò)在Kotlin中則沒(méi)有此限制晴圾,它通過(guò)自動(dòng)包裝實(shí)現(xiàn)了capture-by-reference(所以它沒(méi)有基本類型)。
(3)本地方法棧(線程私有)
與虛擬機(jī)棧的作用相似噪奄,其區(qū)別為虛擬機(jī)棧執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)死姚,而本地方法棧則是為虛擬機(jī)所使用的Native方法人乓。
(4)堆(線程共享)
是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域都毒,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建色罚。此內(nèi)存區(qū)域唯一的目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存温鸽。
Java堆是垃圾收集器管理的主要區(qū)域保屯,因此很多時(shí)候也被稱為“GC堆”(Garbage Collected Heap)。由于現(xiàn)在收集器基本都是采用的分代收集算法涤垫,所以Java堆中還可以細(xì)分為:新生代和老年代姑尺。
(5)方法區(qū)(線程共享)
方法區(qū)用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量蝠猬、靜態(tài)變量切蟋、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
具體來(lái)講榆芦,對(duì)于我們?cè)贘ava程序中使用的每一個(gè)類柄粹,它都會(huì)在方法區(qū)生成一個(gè)對(duì)應(yīng)的class文件,這個(gè)文件記錄的信息有:
(如果你想了解更多的信息,可以參考http://blog.csdn.net/luanlouis/article/details/39892027)匆绣,這里只簡(jiǎn)單的說(shuō)明一下驻右。
1.類信息
2.字段信息
3.方法信息
4.常量池
5.類變量(靜態(tài)static字段,或者companion object)
6.classLoader的引用
7.class對(duì)象的引用
8.方法表
正由于在JVM的方法區(qū)中實(shí)時(shí)記錄了這些信息,我們才可以在運(yùn)行時(shí)獲取類的全部信息崎淳,其關(guān)鍵在于獲取其對(duì)應(yīng)的Class對(duì)象堪夭。
獲取Class對(duì)象
在Java中,獲取Class對(duì)象有以下幾種方法:
1: Class.forName("類名字符串") (注意:類名字符串必須是全稱拣凹,包名+類名)
2: 類名.class
3: 實(shí)例對(duì)象.getClass()
public void getClassTest()
{
try{
Class baseInfo = Class.forName("com.suiseiseki.www.BaseInfo");
Class object = Object.class;
Class date = (new Date()).getClass();
Class testclass = this.getClass();
}
catch (Exception e)
{
e.printStackTrace();
}
}
還原類的信息
獲取類的構(gòu)造器
Java提供以下Api用于獲取類的構(gòu)造方法:
// 獲取“參數(shù)是parameterTypes”的public的構(gòu)造函數(shù)
public Constructor getConstructor(Class[] parameterTypes)
// 獲取全部的public的構(gòu)造函數(shù)
public Constructor[] getConstructors()
// 獲取“參數(shù)是parameterTypes”的森爽,并且是類自身聲明的構(gòu)造函數(shù),包含public嚣镜、protected和private方法爬迟。
public Constructor getDeclaredConstructor(Class[] parameterTypes)
// 獲取類自身聲明的全部的構(gòu)造函數(shù),包含public菊匿、protected和private方法付呕。
public Constructor[] getDeclaredConstructors()
// 如果這個(gè)類是“其它類的構(gòu)造函數(shù)中的內(nèi)部類”,調(diào)用getEnclosingConstructor()就是這個(gè)類所在的構(gòu)造函數(shù)跌捆;若不存在凡涩,返回null。
public Constructor getEnclosingConstructor()
例如:
public class Test1 {
public void testConstructor()
{
try{
Class c = Country.class;
//獲取public的無(wú)參數(shù)構(gòu)造器
Constructor origin = c.getDeclaredConstructor();
//獲取private的構(gòu)造器(注意int.class不是Integer.class)
Constructor cst2 = c.getDeclaredConstructor(new Class[]{int.class,int.class});
//構(gòu)造器是private的疹蛉,所以這里要設(shè)置為可訪問(wèn)
cst2.setAccessible(true);
Country c1 = (Country)origin.newInstance();
Country c2 = (Country)cst2.newInstance(30,100);
System.out.println(c1);
System.out.println(c2);
}
catch (Exception e) {}
}
}
class Country{
public int pop;
public int money;
public Country(){
pop = 0;
money = 0;
}
private Country(int pop,int money)
{
this.pop = pop;
this.money = money;
}
@Override
public String toString()
{
return "pop "+pop+" money: "+money;
}
public void doublePop()
{
this.pop = this.pop * 2;
}
private int multiMoney(int n)
{
this.money = this.money*n;
return this.money;
}
}
在獲取到構(gòu)造器后活箕,可以調(diào)用構(gòu)造器的newInstance創(chuàng)建對(duì)象。例子中可以看到,反射是可以訪問(wèn)類的private域的育韩,而且還可以修改它克蚂,使用被隱藏的構(gòu)造器。
獲取類的方法
在Java中筋讨,方法是作為Method對(duì)象包裝的埃叭,獲取類的方法對(duì)象的Api如下:
// 獲取“名稱是name,參數(shù)是parameterTypes”的public的函數(shù)(包括從基類繼承的悉罕、從接口實(shí)現(xiàn)的所有public函數(shù))
public Method getMethod(String name, Class[] parameterTypes)
// 獲取全部的public的函數(shù)(包括從基類繼承的赤屋、從接口實(shí)現(xiàn)的所有public函數(shù))
public Method[] getMethods()
// 獲取“名稱是name,參數(shù)是parameterTypes”壁袄,并且是類自身聲明的函數(shù)类早,包含public、protected和private方法嗜逻。
public Method getDeclaredMethod(String name, Class[] parameterTypes)
// 獲取全部的類自身聲明的函數(shù)涩僻,包含public、protected和private方法栈顷。
public Method[] getDeclaredMethods()
// 如果這個(gè)類是“其它類中某個(gè)方法的內(nèi)部類”逆日,調(diào)用getEnclosingMethod()就是這個(gè)類所在的方法;若不存在萄凤,返回null室抽。
public Method getEnclosingMethod()
例如,以下方法獲取方法并調(diào)用:
public void testMethod()
{
try{
Class c = Country.class;
Country country3 = new Country();
country3.money = 100;
country3.pop = 3;
//獲取public方法(無(wú)參數(shù)靡努,無(wú)返回值)
Method mDoublePop = c.getMethod("doublePop",new Class[]{});
//調(diào)用invoke執(zhí)行方法坪圾,需要傳入一個(gè)該類的對(duì)象
mDoublePop.invoke(country3);
System.out.println(country3);
//獲取public方法(有參數(shù),有返回值)
Method mMultimoney = c.getMethod("multiMoney", new Class[]{int.class});
mMultimoney.setAccessible(true);
mMultimoney.invoke(country3,42);
System.out.println(country3);
}
catch (Exception e) {}
}
對(duì)應(yīng)方法:
public void doublePop()
{
this.pop = this.pop * 2;
}
private int multiMoney(int n)
{
this.money = this.money*n;
return this.money;
}
獲取類的成員變量
在Java中颤难,成員變量稱為Field對(duì)象,獲取類成員變量的Api如下:
// 獲取“名稱是name”的public的成員變量(包括從基類繼承的、從接口實(shí)現(xiàn)的所有public成員變量)
public Field getField(String name)
// 獲取全部的public成員變量(包括從基類繼承的已维、從接口實(shí)現(xiàn)的所有public成員變量)
public Field[] getFields()
// 獲取“名稱是name”行嗤,并且是類自身聲明的成員變量,包含public垛耳、protected和private成員變量栅屏。
public Field getDeclaredField(String name)
// 獲取全部的類自身聲明的成員變量,包含public堂鲜、protected和private成員變量栈雳。
public Field[] getDeclaredFields()
例如:
public void fieldTest()
{
try{
Class c = Country.class;
Country country4 = new Country();
country4.money = 100;
country4.pop = 3;
Field fPop = c.getField("pop");
fPop.set(42,country4);
System.out.println(country4);
}
catch (Exception e) {}
}
注意權(quán)限的問(wèn)題,如果沒(méi)有權(quán)限缔莲,需要setAccessible(true)哥纫,否則會(huì)拋出異常
類的其它信息
1.注解
// 獲取類的"annotationClass"類型的注解 (包括從基類繼承的、從接口實(shí)現(xiàn)的所有public成員變量)
public Annotation<A> getAnnotation(Class annotationClass)
// 獲取類的全部注解 (包括從基類繼承的痴奏、從接口實(shí)現(xiàn)的所有public成員變量)
public Annotation[] getAnnotations()
// 獲取類自身聲明的全部注解 (包含public蛀骇、protected和private成員變量)
public Annotation[] getDeclaredAnnotations()
現(xiàn)在厌秒,我們可以編寫一些程序來(lái)自動(dòng)處理程序中的注解了,例如根據(jù)注解來(lái)決定是否處理一個(gè)類:
public void handleMyAnnotation(List<Class> list)
{
for(Class clazz : list)
{
if(clazz.getAnnotation(MyAnnotation.class))
{
println("This class is under Annotation");
}
}
}
很多著名的開源庫(kù)(Dagger2,GSON,Retrofit,AspectJ)等都是通過(guò)反射+注解完成的擅憔,這些庫(kù)在方便了編寫程序的同時(shí)也會(huì)帶來(lái)一些性能開銷(盡管它們自身已經(jīng)盡力地做了優(yōu)化),
反射在把裝載期做的事情搬到了運(yùn)行期鸵闪,因此編譯器沒(méi)法對(duì)反射相關(guān)的代碼做優(yōu)化。
2.接口和基類
// 獲取實(shí)現(xiàn)的全部接口
public Type[] getGenericInterfaces()
// 獲取基類
public Type getGenericSuperclass()
注意反射獲取的基類是直接的基類(也就是說(shuō)只能獲取上一級(jí)),要獲取繼承鏈暑诸,需要進(jìn)一步深度遍歷
3.描述性信息
// 獲取“類名”
public String getSimpleName()
// 獲取“完整類名”
public String getName()
// 類是不是“枚舉類”
public boolean isEnum()
// obj是不是類的對(duì)象
public boolean isInstance(Object obj)
// 類是不是“接口”
public boolean isInterface()
// 類是不是“本地類”蚌讼。本地類,就是定義在方法內(nèi)部的類。
public boolean isLocalClass()
// 類是不是“成員類”个榕。成員類,是內(nèi)部類的一種篡石,但是它不是“內(nèi)部類”或“匿名類”。
public boolean isMemberClass()
// 類是不是“基本類型”笛洛。 基本類型夏志,包括void和boolean、byte苛让、char沟蔑、short、int狱杰、long瘦材、float 和 double這幾種類型。
public boolean isPrimitive()
在Kotlin中使用Java中的反射
作為基于JVM的語(yǔ)言仿畸,Kotlin當(dāng)然也支持Java語(yǔ)言中原有的反射機(jī)制(而且代碼量往往更少)食棕,通過(guò)類的javaClass實(shí)現(xiàn)。
例如错沽,一個(gè)常見(jiàn)的通過(guò)反射來(lái)獲取R文件中控件的ID描述的程序:
val viewId : String by lazy {
val c = R.id()
val fields = c.javaClass.declaredFields
val r = fields.find { it.getInt(c) == view.id }?.name?:"Not found"
r
}
使用懶加載避免了無(wú)用的性能開銷簿晓。
需要注意的是直接調(diào)用R.id::class獲取的是KClass對(duì)象,它代表Kotlin中反射的入口千埃,要獲取Java的Class對(duì)象憔儿,需要其.java屬性,例如:
{
val c = R.id::class
val fields = c.java.fields
val r = fields.find { it.getInt(c) == view.id }?.name?:"Not found"
r
}
以上兩種方式代表了通過(guò)對(duì)象和類名訪問(wèn)Java原有反射API的方式放可。
Kotlin中的KClass反射
Kotlin是函數(shù)式編程語(yǔ)言谒臼,它有一些獨(dú)有的特性,例如耀里,在Kotlin中的Property往往對(duì)應(yīng)了Java中的Field以及對(duì)應(yīng)的getter/setter,
而函數(shù)本身也具有類型蜈缤,也可以作為變量保存。
要使用Kotlin的反射Api,需要獲取對(duì)應(yīng)的KClass對(duì)象冯挎,可以通過(guò)以下方式:
1.類名::class
val clazz = Country::class
2.對(duì)象.javaclass.kotlin
val clazz = country4.javaClass.kotlin
KClass是一個(gè)泛型接口底哥,它的定義如下:
public interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {
//返回類的名字
public val simpleName: String?
//返回類的全包名
public val qualifiedName: String?
//返回這個(gè)類可訪問(wèn)的所有函數(shù)和屬性,包括繼承自基類的,但是不包括構(gòu)造器
override val members: Collection<KCallable<*>>
//返回這個(gè)類的所有構(gòu)造器
public val constructors: Collection<KFunction<T>>
//返回這個(gè)類中定義的其他類叠艳,包括內(nèi)部類(inner class聲明的)和嵌套類(class聲明的)
public val nestedClasses: Collection<KClass<*>>
//如果這個(gè)類聲明為object奶陈,則返回其實(shí)例,否則返回null
public val objectInstance: T?
// 判斷一個(gè)對(duì)象是否為此類的實(shí)例
// 和 對(duì)象 is 類名 作用一樣附较,如: country3 is Country
@SinceKotlin("1.1")
public fun isInstance(value: Any?): Boolean
// 返回這個(gè)類的泛型列表
@SinceKotlin("1.1")
public val typeParameters: List<KTypeParameter>
//以列表的方式依次顯示其直接基類
@SinceKotlin("1.1")
public val supertypes: List<KType>
// 返回這個(gè)類的可見(jiàn)性
@SinceKotlin("1.1")
public val visibility: KVisibility?
// 這個(gè)類是否為final類(PS:在Kotlin中吃粒,類默認(rèn)是final的,除非這個(gè)類聲明為open或者abstract)
@SinceKotlin("1.1")
public val isFinal: Boolean
// 這個(gè)類是否是open的(abstract類也是open的)拒课,表示這個(gè)類可以被繼承
@SinceKotlin("1.1")
public val isOpen: Boolean
//是否為抽象類
@SinceKotlin("1.1")
public val isAbstract: Boolean
//判斷是否為密封類
/* 密封類:用sealed修飾徐勃,其子類只能在其內(nèi)部定義 */
@SinceKotlin("1.1")
public val isSealed: Boolean
// 判斷類是否為data類
@SinceKotlin("1.1")
public val isData: Boolean
// 判斷類是否為內(nèi)部類(嵌套類為nest,不算)
@SinceKotlin("1.1")
public val isInner: Boolean
//判斷這個(gè)類是否為companion object
@SinceKotlin("1.1")
public val isCompanion: Boolean
}
除此之外,KClass還有一些很有用的擴(kuò)展函數(shù)/屬性早像,例如:
//返回其所有的基類
val KClass<*>.allSuperclasses: Collection<KClass<*>>
//返回其companionObject
val KClass<*>.companionObject: KClass<*>?
//返回其聲明的所有函數(shù)
val KClass<*>.declaredFunctions: Collection<KFunction<*>>
//返回其擴(kuò)展函數(shù)和屬性
val KClass<*>.declaredMemberExtensionFunctions: Collection<KFunction<*>>
val <T : Any> KClass<T>.declaredMemberExtensionProperties: Collection<KProperty2<T, *, *>>
//返回其自身聲明的函數(shù)和屬性
val KClass<*>.declaredMemberFunctions: Collection<KFunction<*>>
val <T : Any> KClass<T>.declaredMemberProperties: Collection<KProperty1<T, *>>
其實(shí)還有很多僻肖,這里就不一一列舉了,它們其實(shí)都可以通過(guò)基本Api然后進(jìn)行filter獲得
需要注意的是卢鹦,在函數(shù)作為一等公民以后臀脏,函數(shù)和屬性具有了共同的接口KCallable,允許你調(diào)用其call方法來(lái)使用函數(shù)或者訪問(wèn)屬性的getter:
class DVT {
fun test()
{
val su = Person("su",24)
val clazz = su.javaClass.kotlin
val list = clazz.members
for(calls in list)
{
when(calls.name)
{
"name" -> print("name is"+calls.call(su))
"age" -> print("age is"+calls.call(su))
"selfDescribe" -> calls.call()
}
}
}
}
data class Person(val name : String,var age : Int)
{
fun selfDescribe() : String
{
return "My name is $name,I am $age years old"
}
}
需要注意冀自,call這個(gè)方法的參數(shù)類型是vararg Any?揉稚,如果你用錯(cuò)誤的類型實(shí)參(數(shù)量不一致或者類型不一致)去調(diào)用是會(huì)報(bào)錯(cuò)的,
為了避免這種情況,你可以用更具體的方式去調(diào)用這個(gè)函數(shù)熬粗。
class DVT {
fun test()
{
val su = Person("su",24)
val clazz = su.javaClass.kotlin
val function1 = Person::selfDescribe
val function2 = Person::grow
function1.invoke(su)
function2.invoke(su,1)
}
}
data class Person(val name : String,var age : Int)
{
fun selfDescribe() : String
{
return "My name is $name,I am $age years old"
}
fun grow(a : Int) : Int
{
age+=a
return age
}
}
function1的類型是KFunction0<String>,function2的類型是KFunction1<Int,Int>,像KFunctionN這樣的接口代表了不同數(shù)量參數(shù)的參數(shù)搀玖,
它們都繼承了KFunction并添加了一個(gè)invoke成員,它擁有數(shù)量剛好的參數(shù)驻呐,包含參數(shù)和返回參數(shù)
這種類型稱為編譯器生成的類型灌诅,你不能找到它們的聲明,你可以使用任意數(shù)量參數(shù)的函數(shù)接口(而不是先聲明一萬(wàn)個(gè)不同參數(shù)數(shù)量的接口)
對(duì)于call函數(shù)含末,它是對(duì)于所有類型通用的手段猜拾,但是不保證安全性。
你也可以反射調(diào)用屬性的getter和setter:
val ageP = Person::age
//通過(guò)setter-call調(diào)用(不安全)
ageP.setter.call(24)
//通過(guò)set()調(diào)用(安全)
ageP.set(su,24)
//通過(guò)getter-call調(diào)用(不安全)
ageP.getter.call()
//通過(guò)get調(diào)用(安全)
ageP.get(su)
所有屬性都是KProperty1的實(shí)例佣盒,它是一個(gè)泛型類KProperty1<R,T>,其中R為接收者類型(文中的Person類),T為屬性類型(文中為Int),
這樣就保證了你對(duì)正確類型的接收者調(diào)用其方法挎袜。
其子類KMutableProperty代表var屬性
兼容問(wèn)題
雖然Kotlin號(hào)稱完全兼容Java,但是從注解和反射的概念來(lái)看沼撕,似乎它并不代表一切Java的工具庫(kù)都可以應(yīng)用到Kotlin中宋雏。
例如通過(guò)對(duì)代碼注解自動(dòng)生成模板代碼的庫(kù)(APT),生成的代碼均為 Java 代碼而非 kt代碼芜飘。對(duì)于一些作用于java文件到class文件轉(zhuǎn)換過(guò)程中的庫(kù)(AspectJ)务豺,實(shí)際上是很無(wú)力的,它不能作用于kt文件,大部分基于這個(gè)過(guò)程的AOP框架都不能正常工作。唯一能正確作用的是.class到.dex轉(zhuǎn)換中的庫(kù)(熱修復(fù),基于Javaassist)嗦明。因此笼沥,如果要引入前兩種第三方庫(kù),需要確認(rèn)它們是否支持Kotlin語(yǔ)言。
對(duì)于代碼注解自動(dòng)生成模板代碼的庫(kù)(Dagger,ButterKnife,DBFlow這些常見(jiàn)的APT庫(kù))奔浅,可以嘗試Kotlin Annotation processing tool馆纳,簡(jiǎn)稱kapt,但是并不保證能完全正常工作