Kotlin专题「二十五」:反射

前言:不要在夕阳西下时幻想,要在旭日东升时努力。

一、概述

  反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有方法和属性;对于任意一个对象,都能调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制

当在 Kotlin 中使用反射时,你可能会使用到两种不同的反射 API。第一种是标准的 Java 反射,定义在包 java.lang.reflect 中,因为 Kotlin 类会被编译成普通的 Java 字节码,Java 反射 API 可以完美支持它们,完全兼容 Kotlin 代码。

第二种是 Kotlin 反射 API,定义在包 kotlin.reflect 中,它能让你访问 Java 中不存在的东西,比如属性和可空类型。但是它的功能并不能完全覆盖 Java 反射 API,有些情况下仍需要使用 Java 的反射。Kotlin 反射 API 不仅限于 Kotlin 类,同样能够访问用 JVM 语言写成的类。

注意:为了减少不使用反射功能的应用程序所需的运行时库的大小,Kotlin 反射 API 被打包成单独的 .jar 文件作为单独分发,即 kotlin-reflect.jar。它不会被默认地添加到新项目的依赖中,需要手动添加依赖库。

build.gradle 文件中加入 Kotlin 反射依赖:

implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")

二、Kotlin反射API:KClass、KCallable、 KFunction、 KProperty

2.1 类引用

  • JAVA
	//方式一
	Class<?> javaRef = ReflectionActivity.class;
	//方式二
	Class<?> javaName = Class.forName("包名.ReflectionActivity");
  • Kotlin
	//kotlin 类 ReflectionActivity 的引用
	val ktRef: KClass<ReflectionActivity> = ReflectionActivity::class
	//java 类 ReflectionActivity 的引用
	val javaRef: Class<ReflectionActivity> = ReflectionActivity::class.java//将给定的KClass实例转化为相对应的Java Class实例

通过 类名::class 获取这个类的对象引用,Kotlin 类引用与 Java 类引用是不同的。要获取 Java 类引用,请在 KClass 实例上使用 .java 属性。

2.2 KClass

Kotlin 反射的 API 的主要入口是 KClass,它代表一个类,对应的是 Java 中的 java.lang.reflect.Class,可以用它列举和访问类中包含的所有声明和它的超类中的声明。

类名::class 的写法会给你一个 KClass 实例,要在运行时获得一个对象的类,先使用 javaClass 属性获得它的 Java 类,这直接等价于 Java 中的 java.lang.Object.getClass(),然后访问该类的 .kotlin 扩展属性,从 Java 切换到 Kotlin 的反射 API:

    class Person(val name: String, val age: Int)
    
	val person = Person("Kotlin", 5)
	val kclass = person.javaClass.kotlin//返回一个KClass<Person>的实例

	println("kclass == ${kclass.simpleName}")//类的名称
	kclass.memberProperties.forEach {//遍历出所有属性名称
		println("遍历属性名 == ${it.name}")
	}

打印数据如下:

kclass == Person
遍历属性名 == age
遍历属性名 == name

打印出了类的名称以及属性名称,并使用 .memberProperties 来收集这个类,以及它的所有超类中定义的全部非扩展属性。

来看看 KClass 类的源码,它包含了大量方便的方法,用于访问类的内容:

public actual interface KClass<T : Any> {
    public actual val simpleName: String?
    public val qualifiedName: String?
    override val members: Collection<KCallable<*>>
    public val constructors: Collection<KFunction<T>>
    public val nestedClasses: Collection<KClass<*>>
    ······
}

另外,前面用都的 memberProperties 都声明了扩展属性,封装在 KClasses 类中。

2.3 KCallable

由类的所有成员组成的列表是一个 KCallable 实例的集合。KCallable 是函数和属性的超接口,它声明了 call 方法,允许你调用对应的函数或者对应属性的 getter:

public actual interface KCallable<out R> {
    public fun call(vararg args: Any?): R
    public fun callBy(args: Map<KParameter, Any?>): R
    ······
}

你把(被引用)函数的实参放在 vararg 列表中提供给它,下面展示了如何通过反射使用 call 来调用一个函数:

import kotlin.reflect.KFunction1

fun shareInt(number: Int) = println(number)
    
fun main() {
	val kFunction: KFunction1<Int, Unit> = ::shareInt
	kFunction?.call(42)//打印42
}

函数引用 ::shareInt 的值 kFunction 是来自反射 API 的 KFunction 类的一个实例,你可以使用 KCallable.call 方法来调用被引用的函数。需要你提供正确的参数,如果指定实参的数目不等于形参的大小,或者实参类型与形参类型不匹配,都会抛出异常。

2.4 KFunction

上面 ::shareInt 表达式的类型是 KFunction1<Int, Unit>,它包含了形参类型和返回类型的信息,1表示和函数接收一个形参,你可以使用 invoke 方法通过这个接口来调用自身函数,也需要对应的形参数目和类型:

import kotlin.reflect.KFunction2

fun sum(x: Int, y: Int) = x + y
	
fun main() {
	val sumKF: KFunction2<Int, Int, Int> = ::sum
	println(sumKF.invoke(1, 2) + sumKF(3, 4))//打印10
}

如果你有一个具体类型的 KFunction,它的形参类型和返回类型是确定的,那么优先使用这个具体类型的 invoke 方法,call 方法是对所有类型都有效的通用手段,但是它不提供类型安全性。

KFunctionN 接口是如何定义的,又是在哪里定义的?

KFunction1,KFunction2 这样的类型代表了不同数量参数的函数。每一类型都继承了 KFunction 并加上一个额外的成员 invoke,它拥有数量刚好的参数。例如:KFunction2 声明了 operator fun invoke(p1: P1, p2: P2): R,其中P1、P2代表着函数的参数类型,而R代表着函数的返回类型。这些类型成为合成的编译器生成类型,在包 kotlin.reflect 是找不到它们的声明。意味着可以使用任意数量参数的函数接口。合成类型的方式减少了 kotlin-reflect.jar 大小,同时避免了对函数类型参数数量的人为限制。

2.5 KProperty

可以在一个 KProperty 实例上调用 call 方法,它会调用该属性的 getter。但是属性接口会为你提供一个更好获取属性值的方式:get 方法。

要访问 get 方法,你需要属性声明的方式来使用正确的属性接口。下面是一个顶层属性表示为 KProperty0 接口的实例,它有一个无参数的 get 方法:

import kotlin.reflect.KMutableProperty0

//顶层属性
var number = 0

fun main() {
	var kProperty: KMutableProperty0<Int> = ::number
	kProperty.setter.call(123)//通过反射调用setter,把123作为实参传递
	println(kProperty.get())//通过get获取属性的值,打印123
}

一个成员属性由一个 KProperty1 的实例表示,它拥有一个单参数的 get 方法。要访问该属性的值,必须提供你需要值所属的对象实例。

import kotlin.reflect.KProperty1

class Person(val name: String, val age: Int)

fun main() {
	val person = Person("Kotlin", 5)
	val memberProperty: KProperty1<Person, Int> = Person::age
	println(memberProperty.get(person))//打印:5 
}

上面的 memberProperty 存储了一个指向属性的引用,然后调用 memberProperty.get(person) 来获取属于具体 person 实例的这个属性值。所以,如果 memberProperty 指向了 Person 类的 age 属性, memberProperty.get(person) 就是动态获取 person.age 值的一种方式。

KProperty1 是一个泛型类,变量 memberProperty 的类型是 KProperty<Person, Int> 其中第一个类型参数表示接受者类型,而第二个类型参数代表属性的类型。这样你只能对正确类型的接收者调用它的 get 方法;而如果是 memberProperty.get("hellowrld") 这样的调用是会编译不通过。

另外,只能使用反射访问定义在最外层或者类中的属性,而不能访问函数的局部变量。
在这里插入图片描述
上面展示了运行时你可以用来访问源码元素的接口的层级结构。因为所有的声明都能被注解,所以代表运行时声明的接口,比如:KClass、KFunction、 KParameter,全部继承了 KAnnotatedElement 接口。KClass 即可以用来表示类也可以用来表示对象,KProperty 可以用来表示任何属性,而它的子类 KMutableProperty 表示一个用 var 声明的可变属性, 可以使用声明在 KProperty 和 KMutableProperty 中的特殊接口 Getter 和 Setter,把属性的访问器当做函数使用。两个访问器都继承了 KFunction。

总结

反射API描述
KClass对象的实际类型,不包含泛型参数,如Map,可通过对象类型名获得
KCallable由类的所有成员组成的列表是一个 KCallable 实例的集合
KFunction描述函数,可通过函数引用,函数所在类的KClass获取
KProperty描述属性,可通过属性引用,属性所在类的KClass获取

源码地址:https://github.com/FollowExcellence/KotlinDemo-master

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才

我是suming,感谢各位的支持和认可,您的点赞就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !

要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值