kotlin入门潜修之特性及其原理篇—反射

本文收录于 kotlin入门潜修专题系列,欢迎学习交流。

创作不易,如有转载,还请备注。

写在前面

君子藏器于身,待时而动。——与君共勉。

反射

java中反射占有举足轻重的地位,很多优秀的框架都离不开反射。那么什么是反射?为什么反射被视为java语言具有动态性的关键?kotlin中的反射又是什么样的?这就是本篇文章要阐述的主题。

反射是指,在运行期间能够动态获取类信息的一种机制,java提供了一套api,可以满足这种需求。

为什么说反射使得java具有了动态性?这就需要先了解下什么静态语言,什么是动态语言了。

我们都知道,编程语言大都具备一些共有的行为操作,比如添加新代码、扩展对象或者定义、修改系统类型等等,而动态语言和静态语言最主要的区分就是在上述行为的执行时机上,动态语言会在运行时执行上述行为,而静态语言上述行为则发生在编译期。

对于java而言,其数据类型等在编译期间就已经确定,也无法在运行的时候使用诸如动态的增加代码、扩展对象等操作,所以认为其是静态语言,而反射的出现改变了这种情况。

反射机制可以让java具备在运行时动态获取类信息的能力,包括一些类型元数据信息、泛型信息等,同时可以动态的修改这些编码信息,诸如此类的,所以说反射机制使java具备了动态语言的特性。

java中的反射

上个小节提到,反射机制让java具备了在运行时获取类信息的能力,因此想了解反射,需要先了解下类是什么。

正常我们所说的类其实是指java中的所有类的统称,而在反射机制中,java为我们专门提供了一个类,那就是首字母大写的Class类(具体类型是java.lang.Class),Class类保存了一个普通java类(可以是任何类)对应的元数据信息。示例如下:

//定义了一个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对应的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,为了便于观察其对应的Class信息,我们在Person中特意定义了多个有不同访问权限组成的成员变量和成员方法,代码执行完后打印如下:

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()

结合打印结果很容易知道上述代码的含义,主要是要注意区分一些相似方法的不同点,现将相似方法的区别整理如下(横向区分):

方法方法区分
getConstructorsgetDeclaredConstructors二者都是用于获取类的构造方法,但getConstructors只能获取公有的构造方法,而getDeclaredConstructors则可以获取该类定义的全部构造方法
getFieldsgetDeclaredFields二者是用于获取类中的字段,getFields只能获取公有字段,getDeclaredFields则可以获取全部字段
getMethodsgetDeclaredMethods二者都是用于获取类中定义的方法,getDeclaredMethods只能获取当前类中定义的方法,不包括超类方法,而getMethods则还能获取父类方法

上面简单展示了Class的用法,反射正是基于上述信息来完成运行时的一些动态操作的,下面来看个使用反射动态操作类信息的例子。如下所示:

    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字段,其默认值是20。现在我们想动态修改其值,但是由于age是私有字段,所以我们无法通过常规的编码来进行修改。那么,此时我们就可以在运行时利用反射机制来解决该问题。

首先,我们遍历Person类的所有成员,把age字段的权限访问符修改为public,然后通过java为我们提供的field对象的set方法完成属性变更,上述代码执行结果打印如下:

before: 20
after: 1

这就是反射的使用案例之一,我们在运行时通过修改类信息来完成我们想要做的工作。反射的使用案例有很多,这个案例只是冰山一角。java相关的反射暂时阐述至此,主要来看下kotlin中的反射。

kotlin中的反射

上节大概阐述了java中的反射,本节将阐述kotlin中的反射机制,kotlin的反射原理和java基本一致,但语法与java中的有点不太一样。

下面,结合案例来看下kotlin关于类信息的一些操作。

首先我们定义一个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"
}

这里定义了一个父类,又定义了一个子类,目的是为了后边可以方便的验证不同方法所表达的不同功能。来看下测试代码:

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为我们暴露的常见的一些获取类信息的接口,当然还有很多获取类其他信息的接口,这里没有罗列出来,可以自己去验证。上面代码执行完成之后,打印如下:

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

相信大多数人都不会仔细看上面的代码及打印的结果,这里贴出来只是来证明下面我们结论的正确性,所以你完全可以略过上述测试代码,直接来看下面的对比列表(横向比较):

属性属性区分
memberPropertiesdeclaredMemberProperties二者都是用于获取类的属性成员信息,但memberProperties用于获取当前类中的所有属性及其超类中的非私有属性,而declaredMemberProperties则只能用于获取该类定义的全部属性
memberFunctionsdeclaredMemberFunctions二者都是用于获取类中定义的成员方法,memberFunctions可用于获取公当前类的所有方法以及其超类的非私有成员方法,declaredMemberFunctions则只能获取当前类中的所有成员方法
memberExtensionPropertiesdeclaredMemberExtensionProperties二者都是用于获取在当前类中定义的扩展属性,memberExtensionProperties可以获取在当前类以及在其父类中定义的扩展属性,而declaredMemberExtensionProperties则只能获取在当前类中定义的属性
memberExtensionFunctionsdeclaredMemberExtensionFunctions二者都是用于获取在当前类中定义的扩展方法,memberExtensionFunctions可以获取在当前类以及在其父类中定义的扩展方法,而declaredMemberExtensionFunctions则只能获取在当前类中定义的方法

从表格中可以看出,实际上kotlin暴露的获取类信息的接口和java中基本一致,至于一些相似接口的区别完全可以通过其命名进行区分。

但是有一点要特别注意,那就是在kotlin中我们使用:: class这种方法实际上获取的是kotlin中的类信息,其对应的类定义是kotlin.reflect.KClass,而不是java中的java.lang.Class,但很多时候我们需要通过kotlin代码获取其在java语言中所对应的类信息(比如android开发中常见的activity跳转,就需要获取目标类在java语言中对应的Class类),这个时候我们可以通过以下方法获取:

//获取对应的java语言中的类信息,注意,这里是通过Student的
//实例来获取Student对应的类信息的。
    val javaClazz = student::class.java//

上面我们获取KClass类信息的时候,是基于类对象来获取的。我们还可以直接通过类名来获取对应的KClass类信息,如下所示:

   val javaClazz = Student::class//我们通过类名来获取对应的类信息

KClass

前面我们演示了获取kotlin中类对应的类信息,这些信息都保存在KClass这个类中,本节将探索下这个类。

首先,通过查找源码我们发现KClass位于kotlin.reflect包中,是个接口,如下所示:

package kotlin.reflect
public interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {
    public val simpleName: String?
    override val members: Collection<KCallable<*>>
//省略其他一些内容...
}

从该接口的定义可以发现,我们常用的获取类成员的入口members、获取类名称的入口simpleName等都在这个接口中,但是并没有找到上面我们用到的诸如declaredFunctions、memberProperties等属性入口,那么我们为什么能使用这些属性?他们到底定义在了哪里?答案是显然的,那就是这些属性是扩展属性!

确实如此,通过编译器生成的KClass.class这个类文件,我们可以发现,这些属性全部是扩展属性,如下所示:

@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 */
//此处省略其他扩展属性的定义...

找到了这些扩展属性的定义之后,更重要的是关注这些属性的类型,因为这在使用反射的时候往往需要用到。分别摘录如下(结合源代码即编译器为我们生成的代码):

//获取该类的所有成员
 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<*>,另一个是获取当前类定义的成员属性的memberProperties,其集合中的元素类型是kotlin.reflect.KProperty1<T, >,而最后一个是获取当前类定义的方法的memberFunctions属性,其集合中的元素类型是KFunction<>。

既然members能够获取所有成员,那么显然可以推断KCallable应该是KProperty1以及KFunction的超类型!通过查找源码我们发现,确实如此,如下所示:

//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> {}

上面代码验证了我们的推断,实际上KCallable可以用于表示任何成员类型。而KFunction<out R>则用于表示方法类型。最后的KProperty1则是用于表示属性成员类型,不过我们发现在kotlin中,KProperty的子类型不止一个,还有KProperty0类型以及KProperty2类型,分别表示获取类静态成员属性对应的属性类型和获取类内部定义的扩展属性对应的属性类型,摘录如下所示:

//获取类静态属性成员对应的属性类型
@kotlin.SinceKotlin public val kotlin.reflect.KClass<*>.staticProperties: kotlin.collections.Collection<kotlin.reflect.KProperty0<*>> /* compiled code */
//获取类内定义的扩展属性成员对应的属性类型
@kotlin.SinceKotlin public val <T : kotlin.Any> kotlin.reflect.KClass<T>.declaredMemberExtensionProperties: kotlin.collections.Collection<kotlin.reflect.KProperty2<T, *, *>> /* compiled code */

但这三者都是继承自KProperty,即KProperty表达的就是属性成员类型。

了解具体返回的类型之后,我们就可以做更多的操作了,比如我们可以针对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 {
//这里我们刻意显示将属性对应的类型标识出来
        var property = it as KProperty<Person>
//在这里可以进行各种判断
        println(property.toString() + " isFinal: " + property.isFinal + " isOpen: " + property.isOpen)
    }
}

方法的操作也是同样道理了,这里不再阐述。

最后来看一个kotlin中使用反射的经典案例。

动态代理

代理模式应该都比较清楚,代理模式屏蔽了目标对象的实现细节,可分为静态代理和动态代理。静态代理由于其无法动态的进行扩展,只能在编码层次无限增加,所以有很大的局限性。而动态代理则可以进行动态扩展,可以拥有一份代理逻辑,却能完成不同的业务逻辑。

代理模式是使用反射的很好的案例,下面我们通过kotlin代码来实现该模式。

首先,在没有第三方库支持的情况下,代理模式中的被代理者需要实现一个接口,这里我们先来定义一个被代理者,如下所示:

//首先为被代理者定义一个接口
interface IOperation {
    fun operation()
}
//然后定义被代理者,实现了IOperation接口
class RealObject : IOperation {
    override fun operation() {
        println("real object operation...")
    }
}

定义完被代理者之后,我们来看下动态代理中最重要的代理逻辑,如下所示:

//代理逻辑代码
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()
}

执行代码,打印如下所示:

proxy start, you can do some work here
real object operation...
proxy end, you can also do some work here

关于上述代码,需要注意以下几点:

  1. 我们提供了一个工厂类,用于获取代理实例,这个代理实例代理了我们的目标对象即被代理者。
  2. newProxyInstance方法需要三个参数,第一个是目标类的类加载器,第二个是目标类实现的接口,第三个是InvocationHandler接口,该接口有个invoke方法。
  3. 代理逻辑实际上就是invoke方法中的逻辑,在这个方法中,我们首先打印了标识代理执行开始的语句,结果利用kotlin反射机制调用了被代理者的目标方法,最后我们打印了标识代理执行结束的语句。从这个流程可以看出,我们既可以在执行被代理者业务逻辑之前做一些工作,也可以在其之后做一些工作,这也是代理模式的另一部分优势!面向切面编程(AOP)就是利用了动态代理这一思想来实现的。
  4. 在invoke方法中,我们对参数args进行了处理,主要是kotlin中method.invoke方法接收两个参数,第一个就是我们的被代理对象,第二个则是被代理的方法参数,但是由于这个参数是可变参数,而我们传入的实际上是个数组,这如果放在java中,编译器可以帮我们自动处理,但是kotlin中我们还需要显示进行处理,处理方法就是在数组之前加上星号(*),这样数组就被转换为了可变参数。
  5. 上述提到了method,那么这个到底是什么?实际上这个就是我们的接口方法,这也是动态代理为什么要求目标者实现接口的原因。打印method的值如下所示:
public abstract void proxy.IOperation.operation()

最后,前面我们说过,动态代理能够在一套代理代码下代理各种对象的执行,那么假如现在我们新增一个代理对象,并且要代理该对象中的有参方法,该怎么实现?

很简单,我们首先定义下我们的代理目标,如下所示:

//定义一个接口
interface IOperation2 {
//定义了一个包含有1个入参的方法
    fun operation(operationDesc: String)
}
//目标对象
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!")

通过上面的代码我们就完成了重新代理一个新对象的需求,我们发现没有对ProxyFactory进行任何变更!这就是动态代理相对于静态代理的优势,上面代码执行完后,打印如下所示:

proxy start, you can do some work here
operation in Real object:  just do it!
proxy end, you can also do some work here

至此,本篇文章阐述完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值