本文收錄于 kotlin入門潛修專題系列,歡迎學(xué)習(xí)交流卫枝。
創(chuàng)作不易煎饼,如有轉(zhuǎn)載,還請備注校赤。
反射
java中反射占有舉足輕重的地位吆玖,很多優(yōu)秀的框架都離不開反射。那么什么是反射马篮?為什么反射被視為java語言具有動態(tài)性的關(guān)鍵沾乘?kotlin中的反射又是什么樣的?這就是本篇文章要闡述的主題浑测。
反射是指翅阵,在運(yùn)行期間能夠動態(tài)獲取類信息的一種機(jī)制歪玲,java提供了一套api,可以滿足這種需求掷匠。
為什么說反射使得java具有了動態(tài)性滥崩?這就需要先了解下什么靜態(tài)語言,什么是動態(tài)語言了讹语。
我們都知道钙皮,編程語言大都具備一些共有的行為操作,比如添加新代碼顽决、擴(kuò)展對象或者定義株灸、修改系統(tǒng)類型等等,而動態(tài)語言和靜態(tài)語言最主要的區(qū)分就是在上述行為的執(zhí)行時機(jī)上擎值,動態(tài)語言會在運(yùn)行時執(zhí)行上述行為慌烧,而靜態(tài)語言上述行為則發(fā)生在編譯期。
對于java而言鸠儿,其數(shù)據(jù)類型等在編譯期間就已經(jīng)確定屹蚊,也無法在運(yùn)行的時候使用諸如動態(tài)的增加代碼、擴(kuò)展對象等操作进每,所以認(rèn)為其是靜態(tài)語言汹粤,而反射的出現(xiàn)改變了這種情況。
反射機(jī)制可以讓java具備在運(yùn)行時動態(tài)獲取類信息的能力田晚,包括一些類型元數(shù)據(jù)信息嘱兼、泛型信息等,同時可以動態(tài)的修改這些編碼信息贤徒,諸如此類的芹壕,所以說反射機(jī)制使java具備了動態(tài)語言的特性。
java中的反射
上個小節(jié)提到接奈,反射機(jī)制讓java具備了在運(yùn)行時獲取類信息的能力踢涌,因此想了解反射,需要先了解下類是什么序宦。
正常我們所說的類其實(shí)是指java中的所有類的統(tǒng)稱睁壁,而在反射機(jī)制中,java為我們專門提供了一個類互捌,那就是首字母大寫的Class類(具體類型是java.lang.Class)潘明,Class類保存了一個普通java類(可以是任何類)對應(yīng)的元數(shù)據(jù)信息。示例如下:
//定義了一個Person類
public class Person {
public Person() {
}
private Person(String name) {
}
private int age;
public String name;
public static void nothing() {
}
public int getAge() {
return age;
}
private void test() {
System.out.println("test");
}
}
//測試類
public class Test {
public static void main(String[] args) {
//我們可以通過這種方式獲取Person對應(yīng)的Class類引用
Class<Person> clazz = Person.class;
System.out.println("getName : " + clazz.getName());
for (Constructor constructor : clazz.getConstructors()){
System.out.println("constructors: " + constructor);
}
for (Constructor constructor : clazz.getDeclaredConstructors()){
System.out.println("declared constructors: " + constructor);
}
for (Field field : clazz.getDeclaredFields()) {
System.out.println("declared field: " + field);
}
for (Field field : clazz.getFields()) {
System.out.println("field: " + field);
}
for (Method method : clazz.getDeclaredMethods()){
System.out.println("declare method: " + method);
}
for (Method method : clazz.getMethods()){
System.out.println("method: " + method);
}
}
}
上面代碼定義了一個普通的類Person秕噪,為了便于觀察其對應(yīng)的Class信息钳降,我們在Person中特意定義了多個有不同訪問權(quán)限組成的成員變量和成員方法,代碼執(zhí)行完后打印如下:
getName : test.Person
constructors: public test.Person()
declared constructors: public test.Person()
declared constructors: private test.Person(java.lang.String)
declared field: private int test.Person.age
declared field: public java.lang.String test.Person.name
field: public java.lang.String test.Person.name
declare method: private void test.Person.test()
declare method: public int test.Person.getAge()
declare method: public static void test.Person.nothing()
method: public int test.Person.getAge()
method: public static void test.Person.nothing()
method: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
method: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
method: public final void java.lang.Object.wait() throws java.lang.InterruptedException
method: public boolean java.lang.Object.equals(java.lang.Object)
method: public java.lang.String java.lang.Object.toString()
method: public native int java.lang.Object.hashCode()
method: public final native java.lang.Class java.lang.Object.getClass()
method: public final native void java.lang.Object.notify()
method: public final native void java.lang.Object.notifyAll()
結(jié)合打印結(jié)果很容易知道上述代碼的含義巢价,主要是要注意區(qū)分一些相似方法的不同點(diǎn)牲阁,現(xiàn)將相似方法的區(qū)別整理如下(橫向區(qū)分):
方法 | 方法 | 區(qū)分 |
---|---|---|
getConstructors | getDeclaredConstructors | 二者都是用于獲取類的構(gòu)造方法固阁,但getConstructors只能獲取公有的構(gòu)造方法,而getDeclaredConstructors則可以獲取該類定義的全部構(gòu)造方法 |
getFields | getDeclaredFields | 二者是用于獲取類中的字段城菊,getFields只能獲取公有字段备燃,getDeclaredFields則可以獲取全部字段 |
getMethods | getDeclaredMethods | 二者都是用于獲取類中定義的方法,getDeclaredMethods只能獲取當(dāng)前類中定義的方法凌唬,不包括超類方法并齐,而getMethods則還能獲取父類方法 |
上面簡單展示了Class的用法,反射正是基于上述信息來完成運(yùn)行時的一些動態(tài)操作的客税,下面來看個使用反射動態(tài)操作類信息的例子况褪。如下所示:
public static void main(String[] args) {
Person person = new Person();
person.setAge(100);
System.out.println("before: " + person.getAge());
Field[] fields = person.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("age")) {
try {
field.set(person, 1);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
System.out.println("after: " + person.getAge());
}
}
我們首先來闡述下我們想要完成的工作:我們定義了一個Person類,在其中定義了一個私有的age字段更耻,其默認(rèn)值是20〔舛猓現(xiàn)在我們想動態(tài)修改其值,但是由于age是私有字段秧均,所以我們無法通過常規(guī)的編碼來進(jìn)行修改食侮。那么,此時我們就可以在運(yùn)行時利用反射機(jī)制來解決該問題目胡。
首先锯七,我們遍歷Person類的所有成員,把a(bǔ)ge字段的權(quán)限訪問符修改為public誉己,然后通過java為我們提供的field對象的set方法完成屬性變更眉尸,上述代碼執(zhí)行結(jié)果打印如下:
before: 20
after: 1
這就是反射的使用案例之一,我們在運(yùn)行時通過修改類信息來完成我們想要做的工作巨双。反射的使用案例有很多噪猾,這個案例只是冰山一角。java相關(guān)的反射暫時闡述至此炉峰,主要來看下kotlin中的反射畏妖。
kotlin中的反射
上節(jié)大概闡述了java中的反射,本節(jié)將闡述kotlin中的反射機(jī)制疼阔,kotlin的反射原理和java基本一致,但語法與java中的有點(diǎn)不太一樣半夷。
下面婆廊,結(jié)合案例來看下kotlin關(guān)于類信息的一些操作。
首先我們定義一個Person類巫橄,如下所示:
open class Person {
private val age = 20
internal var name = "name"
private fun getAge(): Int {
return age
}
fun personName(): String {
return name
}
fun String.charAtIndexInPerson(index: Int): Char {
return ' '
}
val String.lastCharInPerson: String
get() = "last char"
}
接著淘邻,我們再定義一個Person類的子類Student,如下所示:
class Student : Person() {
private val grade = "一年級"
val credit = 90
private fun getGrade(): String {
return grade
}
fun studentCredit(): Int {
return credit
}
fun String.charAtIndexInStudent(index: Int): Char {
return ' '
}
val String.lastCharInStudent: String
get() = "last char"
}
這里定義了一個父類湘换,又定義了一個子類宾舅,目的是為了后邊可以方便的驗(yàn)證不同方法所表達(dá)的不同功能统阿。來看下測試代碼:
fun main(args: Array<String>) {
val student = Student()
val clazz = student::class
println(clazz)
clazz.memberProperties.forEach {
println("memberProperties: " + it)
}
clazz.declaredMemberProperties.forEach {
println("declaredMemberProperties: " + it)
}
clazz.memberFunctions.forEach {
println("memberFunctions: " + it)
}
clazz.declaredMemberFunctions.forEach {
println("declaredMemberFunctions: " + it)
}
clazz.memberExtensionProperties.forEach {
println("memberExtensionProperties: " + it)
}
clazz.declaredMemberExtensionProperties.forEach {
println("declaredMemberExtensionProperties: " + it)
}
clazz.memberExtensionFunctions.forEach {
println("memberExtensionFunctions: " + it)
}
clazz.declaredMemberExtensionFunctions.forEach {
println("declaredMemberExtensionFunctions: " + it)
}
}
上面代碼演示了kotlin為我們暴露的常見的一些獲取類信息的接口,當(dāng)然還有很多獲取類其他信息的接口筹我,這里沒有羅列出來扶平,可以自己去驗(yàn)證。上面代碼執(zhí)行完成之后蔬蕊,打印如下:
class Student
memberProperties: val Student.credit: kotlin.Int
memberProperties: val Student.grade: kotlin.String
memberProperties: var Person.name: kotlin.String
declaredMemberProperties: val Student.credit: kotlin.Int
declaredMemberProperties: val Student.grade: kotlin.String
memberFunctions: fun Student.getGrade(): kotlin.String
memberFunctions: fun Student.studentCredit(): kotlin.Int
memberFunctions: fun kotlin.Any.equals(kotlin.Any?): kotlin.Boolean
memberFunctions: fun kotlin.Any.hashCode(): kotlin.Int
memberFunctions: fun Person.personName(): kotlin.String
memberFunctions: fun kotlin.Any.toString(): kotlin.String
declaredMemberFunctions: fun Student.getGrade(): kotlin.String
declaredMemberFunctions: fun Student.studentCredit(): kotlin.Int
memberExtensionProperties: val Student.(kotlin.String.)lastCharInStudent: kotlin.String
memberExtensionProperties: val Person.(kotlin.String.)lastCharInPerson: kotlin.String
declaredMemberExtensionProperties: val Student.(kotlin.String.)lastCharInStudent: kotlin.String
memberExtensionFunctions: fun Student.(kotlin.String.)charAtIndexInStudent(kotlin.Int): kotlin.Char
memberExtensionFunctions: fun Person.(kotlin.String.)charAtIndexInPerson(kotlin.Int): kotlin.Char
declaredMemberExtensionFunctions: fun Student.(kotlin.String.)charAtIndexInStudent(kotlin.Int): kotlin.Char
相信大多數(shù)人都不會仔細(xì)看上面的代碼及打印的結(jié)果结澄,這里貼出來只是來證明下面我們結(jié)論的正確性,所以你完全可以略過上述測試代碼岸夯,直接來看下面的對比列表(橫向比較):
屬性 | 屬性 | 區(qū)分 |
---|---|---|
memberProperties | declaredMemberProperties | 二者都是用于獲取類的屬性成員信息麻献,但memberProperties用于獲取當(dāng)前類中的所有屬性及其超類中的非私有屬性,而declaredMemberProperties則只能用于獲取該類定義的全部屬性 |
memberFunctions | declaredMemberFunctions | 二者都是用于獲取類中定義的成員方法猜扮,memberFunctions可用于獲取公當(dāng)前類的所有方法以及其超類的非私有成員方法勉吻,declaredMemberFunctions則只能獲取當(dāng)前類中的所有成員方法 |
memberExtensionProperties | declaredMemberExtensionProperties | 二者都是用于獲取在當(dāng)前類中定義的擴(kuò)展屬性,memberExtensionProperties可以獲取在當(dāng)前類以及在其父類中定義的擴(kuò)展屬性旅赢,而declaredMemberExtensionProperties則只能獲取在當(dāng)前類中定義的屬性 |
memberExtensionFunctions | declaredMemberExtensionFunctions | 二者都是用于獲取在當(dāng)前類中定義的擴(kuò)展方法餐曼,memberExtensionFunctions可以獲取在當(dāng)前類以及在其父類中定義的擴(kuò)展方法,而declaredMemberExtensionFunctions則只能獲取在當(dāng)前類中定義的方法 |
從表格中可以看出鲜漩,實(shí)際上kotlin暴露的獲取類信息的接口和java中基本一致源譬,至于一些相似接口的區(qū)別完全可以通過其命名進(jìn)行區(qū)分。
但是有一點(diǎn)要特別注意孕似,那就是在kotlin中我們使用:: class這種方法實(shí)際上獲取的是kotlin中的類信息踩娘,其對應(yīng)的類定義是kotlin.reflect.KClass,而不是java中的java.lang.Class喉祭,但很多時候我們需要通過kotlin代碼獲取其在java語言中所對應(yīng)的類信息(比如android開發(fā)中常見的activity跳轉(zhuǎn)养渴,就需要獲取目標(biāo)類在java語言中對應(yīng)的Class類),這個時候我們可以通過以下方法獲确豪印:
//獲取對應(yīng)的java語言中的類信息理卑,注意,這里是通過Student的
//實(shí)例來獲取Student對應(yīng)的類信息的蔽氨。
val javaClazz = student::class.java//
上面我們獲取KClass類信息的時候藐唠,是基于類對象來獲取的。我們還可以直接通過類名來獲取對應(yīng)的KClass類信息鹉究,如下所示:
val javaClazz = Student::class//我們通過類名來獲取對應(yīng)的類信息
KClass
前面我們演示了獲取kotlin中類對應(yīng)的類信息宇立,這些信息都保存在KClass這個類中,本節(jié)將探索下這個類自赔。
首先妈嘹,通過查找源碼我們發(fā)現(xiàn)KClass位于kotlin.reflect包中,是個接口绍妨,如下所示:
package kotlin.reflect
public interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {
public val simpleName: String?
override val members: Collection<KCallable<*>>
//省略其他一些內(nèi)容...
}
從該接口的定義可以發(fā)現(xiàn)润脸,我們常用的獲取類成員的入口members柬脸、獲取類名稱的入口simpleName等都在這個接口中,但是并沒有找到上面我們用到的諸如declaredFunctions毙驯、memberProperties等屬性入口倒堕,那么我們?yōu)槭裁茨苁褂眠@些屬性?他們到底定義在了哪里尔苦?答案是顯然的涩馆,那就是這些屬性是擴(kuò)展屬性!
確實(shí)如此允坚,通過編譯器生成的KClass.class這個類文件魂那,我們可以發(fā)現(xiàn),這些屬性全部是擴(kuò)展屬性稠项,如下所示:
@kotlin.SinceKotlin public val kotlin.reflect.KClass<*>.declaredFunctions: kotlin.collections.Collection<kotlin.reflect.KFunction<*>> /* compiled code */
@kotlin.SinceKotlin public val kotlin.reflect.KClass<*>.declaredMemberExtensionFunctions: kotlin.collections.Collection<kotlin.reflect.KFunction<*>> /* compiled code */
//此處省略其他擴(kuò)展屬性的定義...
找到了這些擴(kuò)展屬性的定義之后涯雅,更重要的是關(guān)注這些屬性的類型,因?yàn)檫@在使用反射的時候往往需要用到展运。分別摘錄如下(結(jié)合源代碼即編譯器為我們生成的代碼):
//獲取該類的所有成員
override val members: Collection<KCallable<*>>
//獲取該類的所有成員屬性
@kotlin.SinceKotlin public val <T : kotlin.Any> kotlin.reflect.KClass<T>.memberProperties: kotlin.collections.Collection<kotlin.reflect.KProperty1<T, *>> /* compiled code */
//獲取該類的所有成員方法
@kotlin.SinceKotlin public val kotlin.reflect.KClass<*>.memberFunctions: kotlin.collections.Collection<kotlin.reflect.KFunction<*>> /* compiled code */
上面摘錄了三個不同類型的屬性活逆,一個是獲取類所有成員的members,其集合中的元素類型是KCallable<*>拗胜,另一個是獲取當(dāng)前類定義的成員屬性的memberProperties蔗候,其集合中的元素類型是kotlin.reflect.KProperty1<T, >,而最后一個是獲取當(dāng)前類定義的方法的memberFunctions屬性埂软,其集合中的元素類型是KFunction<>锈遥。
既然members能夠獲取所有成員,那么顯然可以推斷KCallable應(yīng)該是KProperty1以及KFunction的超類型勘畔!通過查找源碼我們發(fā)現(xiàn)所灸,確實(shí)如此,如下所示:
//KFunction類型炫七,繼承自KCallable
public interface KFunction<out R> : KCallable<R>, Function<R> {}
//從下面可以看出爬立,KProperty1繼承自KProperty
public interface KProperty1<T, out R> : KProperty<R>, (T) -> R {}
//而KProperty繼承自KCallable
public interface KProperty<out R> : KCallable<R> {}
上面代碼驗(yàn)證了我們的推斷,實(shí)際上KCallable可以用于表示任何成員類型万哪。而KFunction<out R>則用于表示方法類型侠驯。最后的KProperty1則是用于表示屬性成員類型,不過我們發(fā)現(xiàn)在kotlin中壤圃,KProperty的子類型不止一個陵霉,還有KProperty0類型以及KProperty2類型,分別表示獲取類靜態(tài)成員屬性對應(yīng)的屬性類型和獲取類內(nèi)部定義的擴(kuò)展屬性對應(yīng)的屬性類型伍绳,摘錄如下所示:
//獲取類靜態(tài)屬性成員對應(yīng)的屬性類型
@kotlin.SinceKotlin public val kotlin.reflect.KClass<*>.staticProperties: kotlin.collections.Collection<kotlin.reflect.KProperty0<*>> /* compiled code */
//獲取類內(nèi)定義的擴(kuò)展屬性成員對應(yīng)的屬性類型
@kotlin.SinceKotlin public val <T : kotlin.Any> kotlin.reflect.KClass<T>.declaredMemberExtensionProperties: kotlin.collections.Collection<kotlin.reflect.KProperty2<T, *, *>> /* compiled code */
但這三者都是繼承自KProperty,即KProperty表達(dá)的就是屬性成員類型乍桂。
了解具體返回的類型之后冲杀,我們就可以做更多的操作了效床,比如我們可以針對KProperty的屬性做做以下判斷:
//定義了一個Person類
class Person {
private val age = 20
var name = "name"
open val address = "address"
}
//測試方法
fun main(args: Array<String>) {
var clazz: KClass<Person> = Person::class
clazz.memberProperties.forEach {
//這里我們刻意顯示將屬性對應(yīng)的類型標(biāo)識出來
var property = it as KProperty<Person>
//在這里可以進(jìn)行各種判斷
println(property.toString() + " isFinal: " + property.isFinal + " isOpen: " + property.isOpen)
}
}
方法的操作也是同樣道理了,這里不再闡述权谁。
最后來看一個kotlin中使用反射的經(jīng)典案例剩檀。
動態(tài)代理
代理模式應(yīng)該都比較清楚,代理模式屏蔽了目標(biāo)對象的實(shí)現(xiàn)細(xì)節(jié)旺芽,可分為靜態(tài)代理和動態(tài)代理沪猴。靜態(tài)代理由于其無法動態(tài)的進(jìn)行擴(kuò)展,只能在編碼層次無限增加采章,所以有很大的局限性运嗜。而動態(tài)代理則可以進(jìn)行動態(tài)擴(kuò)展,可以擁有一份代理邏輯悯舟,卻能完成不同的業(yè)務(wù)邏輯担租。
代理模式是使用反射的很好的案例,下面我們通過kotlin代碼來實(shí)現(xiàn)該模式抵怎。
首先奋救,在沒有第三方庫支持的情況下,代理模式中的被代理者需要實(shí)現(xiàn)一個接口反惕,這里我們先來定義一個被代理者尝艘,如下所示:
//首先為被代理者定義一個接口
interface IOperation {
fun operation()
}
//然后定義被代理者,實(shí)現(xiàn)了IOperation接口
class RealObject : IOperation {
override fun operation() {
println("real object operation...")
}
}
定義完被代理者之后姿染,我們來看下動態(tài)代理中最重要的代理邏輯背亥,如下所示:
//代理邏輯代碼
class ProxyFactory {
companion object {
fun getInstance(targetObj: Any): Any? {
return Proxy.newProxyInstance(targetObj.javaClass.classLoader, targetObj.javaClass.interfaces, { proxy, method, args ->
println("proxy start, you can do some work here")
val newArgs = args ?: arrayOfNulls(0)
method.invoke(targetObj, *newArgs)
println("proxy end, you can also do some work here")
})
}
}
}
測試代碼如下所示:
fun main(args: Array<String>) {
val operation: IOperation = ProxyFactory.getInstance(RealObject()) as IOperation
operation.operation()
}
執(zhí)行代碼,打印如下所示:
proxy start, you can do some work here
real object operation...
proxy end, you can also do some work here
關(guān)于上述代碼盔粹,需要注意以下幾點(diǎn):
- 我們提供了一個工廠類隘梨,用于獲取代理實(shí)例,這個代理實(shí)例代理了我們的目標(biāo)對象即被代理者舷嗡。
- newProxyInstance方法需要三個參數(shù)轴猎,第一個是目標(biāo)類的類加載器,第二個是目標(biāo)類實(shí)現(xiàn)的接口进萄,第三個是InvocationHandler接口捻脖,該接口有個invoke方法。
- 代理邏輯實(shí)際上就是invoke方法中的邏輯中鼠,在這個方法中可婶,我們首先打印了標(biāo)識代理執(zhí)行開始的語句,結(jié)果利用kotlin反射機(jī)制調(diào)用了被代理者的目標(biāo)方法援雇,最后我們打印了標(biāo)識代理執(zhí)行結(jié)束的語句矛渴。從這個流程可以看出,我們既可以在執(zhí)行被代理者業(yè)務(wù)邏輯之前做一些工作,也可以在其之后做一些工作具温,這也是代理模式的另一部分優(yōu)勢蚕涤!面向切面編程(AOP)就是利用了動態(tài)代理這一思想來實(shí)現(xiàn)的。
- 在invoke方法中铣猩,我們對參數(shù)args進(jìn)行了處理揖铜,主要是kotlin中method.invoke方法接收兩個參數(shù),第一個就是我們的被代理對象达皿,第二個則是被代理的方法參數(shù)天吓,但是由于這個參數(shù)是可變參數(shù),而我們傳入的實(shí)際上是個數(shù)組峦椰,這如果放在java中龄寞,編譯器可以幫我們自動處理,但是kotlin中我們還需要顯示進(jìn)行處理们何,處理方法就是在數(shù)組之前加上星號(*)萄焦,這樣數(shù)組就被轉(zhuǎn)換為了可變參數(shù)。
- 上述提到了method冤竹,那么這個到底是什么拂封?實(shí)際上這個就是我們的接口方法,這也是動態(tài)代理為什么要求目標(biāo)者實(shí)現(xiàn)接口的原因鹦蠕。打印method的值如下所示:
public abstract void proxy.IOperation.operation()
最后冒签,前面我們說過,動態(tài)代理能夠在一套代理代碼下代理各種對象的執(zhí)行钟病,那么假如現(xiàn)在我們新增一個代理對象萧恕,并且要代理該對象中的有參方法,該怎么實(shí)現(xiàn)肠阱?
很簡單票唆,我們首先定義下我們的代理目標(biāo),如下所示:
//定義一個接口
interface IOperation2 {
//定義了一個包含有1個入?yún)⒌姆椒? fun operation(operationDesc: String)
}
//目標(biāo)對象
class RealObject2 : IOperation2 {
override fun operation(operationDesc: String) {
println("operation in Real object: " + operationDesc)
}
}
//測試方法
val operation: IOperation2 = ProxyFactory.getInstance(RealObject2()) as IOperation2
operation.operation(" just do it!")
通過上面的代碼我們就完成了重新代理一個新對象的需求屹徘,我們發(fā)現(xiàn)沒有對ProxyFactory進(jìn)行任何變更走趋!這就是動態(tài)代理相對于靜態(tài)代理的優(yōu)勢,上面代碼執(zhí)行完后噪伊,打印如下所示:
proxy start, you can do some work here
operation in Real object: just do it!
proxy end, you can also do some work here
至此簿煌,本篇文章闡述完畢。