前言
在上一篇文章中我们介绍了Kotlin
中的集合的使用,这篇文章我们来介绍Kotlin
中反射的应用。
1.获取类引用
相信使用Kotlin
开发Android
的读者,对于下面这行代码都会很熟悉:
ini
val TAG = MainActivity::class.simpleName
我们知道反射其实是用来获取类运行时的信息,它的语法结构看上去也比较简单:
arduino
ClassName::class
在类名后添加::class
这意味着我们获取指定类的类引用,在Kotlin
中该引用是KClass
类型的值。如果要获取Java
的类引用,我们需要在KClass
示例上加上.java
属性。KClass
是Kotlin
中为我们提供的一个获取运行时类信息的类,该类包含了我们在运行时所需要的各种属性,如下代码示例:
kotlin
public actual interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {
public actual val simpleName: String?
public actual val qualifiedName: String?
public val constructors: Collection<KFunction<T>>
public actual fun isInstance(value: Any?): Boolean
// ...省略
}
这里为了方便阅读,对源码进行了一些删减。只保留了几个常见的运行时属性和一个isInstance()
方法。我们知道Kotlin
代码最终还是要编译成Java
字节码的,下面我们就来看一下这行代码在Java中是如何实现的:
ini
val TAG = MainActivity::class.simpleName
在Android Studio中依次打开Tools -> Show Kotlin Bycode ->在右边的弹出框中我们点击Decopile按钮:
arduino
public static final void main() {
String TAG = Reflection.getOrCreateKotlinClass(MainActivity.class).getSimpleName();
}
接着我们顺着getOrCreateKotlinClass()
方法依次查看其调用链:
kotlin
public static KClass getOrCreateKotlinClass(Class javaClass) {
return factory.getOrCreateKotlinClass(javaClass);
}
public KClass getOrCreateKotlinClass(Class javaClass) {
return new ClassReference(javaClass);
}
public class ClassReference(override val jClass: Class<*>) : KClass<Any>, ClassBasedDeclarationContainer {
override val simpleName: String?
get() = getClassSimpleName(jClass)
...
}
public fun getClassQualifiedName(jClass: Class<*>): String? = when {
...
else -> classFqNames[jClass.name] ?: jClass.canonicalName
}
canonicalName
属性就是通过Java
中Class
对象的getSimpleName()
方法来获取的。到这里我们可以看到KClass
对象在编译期间还是通过Java
中的Class
对象来间接帮助我们获取运行时信息。
2.可调用的引用
函数、属性以及构造函数的引用,还可以用于调用或者用作函数类型的实例。所有可调用引用的公共超类型是KCallable<out R>
,其中R是返回值类型,对于属性是属性类型,对于构造函数是所构造类型。
3.属性引用
在介绍by关键字的文章中,我们在使用属性委托时,在代理中的setValue()
方法中就要求我们必须要传被代理属性的引用,下面我们再来看下该方法的实现:
kotlin
class Delegate {
operator fun getValue(thisRef:Any?, prop:KProperty<*>) : String {
return "$thisRef, name = ${prop.name}"
}
}
这里的KProperty
就是我们Kotlin
中封装的一个存储属性相关信息的接口,它继承自KCallable
接口。下面我们来看一个简单的例子:
kotlin
val name: String get() = "name"
fun main() {
val property = ::name
println(property.name)
}
反编译成Java
的代码如下:
java
final class ReferenceClass extends PropertyReference0Impl {
public static final KProperty0 INSTANCE = new ReferenceClass();
ReferenceClass() {
super(ReferenceKt.class, "name", "getName()Ljava/lang/String;", 1);
}
public Object get() {
return ReferenceKt.getName();
}
}
public final class ReferenceKt {
public static final String getName() {
return "name";
}
public static final void main() {
KProperty0 property = ReferenceClass.INSTANCE;
String var1 = property.getName();
System.out.println(var1);
}
}
为了方便阅读,这里对反编译的代码做了一些调整。PropertyReference0Impl
这个类实现了 KProperty
接口。在mian()
函数中我们创建了ReferenceClass
对象,然后获取name
属性的值。到这里我们可以看到一个属性引用它真实的实现过程。
4.函数引用
在前面的文章中我们介绍到在Kotlin
中我们使用Lambda
表达式来初始化一个函数类型,也就是说Lambda
表达式是函数类型的实例。今天我们来介绍另外一种获取函数类型实例的方法,使用 ::
运算符。下面我们就来看一个简单的示例:
kotlin
fun main() {
val refMethod = ::test
normal(refMethod)
val block: () -> Unit = { println("block called.") }
normal(block)
}
inline fun normal(block: () -> Unit) {
block.invoke()
}
fun test() {
println("test method called.")
}
反编译后获取到Java
代码如下:
java
public final class ReferenceKt {
public static final void main() {
KFunction refMethod = null.INSTANCE;
int $i$f$normal = false;
((Function0)refMethod).invoke();
Function0 block = (Function0)null.INSTANCE;
int $i$f$normal = false;
block.invoke();
}
public static void main(String[] var0) {
main();
}
public static final void normal(@NotNull Function0 block) {
int $i$f$normal = 0;
Intrinsics.checkNotNullParameter(block, "block");
block.invoke();
}
public static final void test() {
String var0 = "test method called.";
System.out.println(var0);
}
}
由反编译后的结果我们可以看到,获取类引用是通过创建KFunction
接口的子类对象。而Lambda
表达式则是创建了Function0
接口的子类对象。KFunction
接口实现了KCallable
接口,而Function0
接口是Lambda
表达式在Java
中的实现方式。
5.构造函数的引用
通常我们获取一个Fragment
的对象,都会使用如下方式:
kotlin
class MainFragment private constructor() : Fragment() {
companion object {
fun getInstance() = MainFragment()
}
}
MainFragment
的构造函数,它也是一个函数类型,等价于:
kotlin
val block: () -> MainFragment
我们可以使用 ::
运算符先来获取该构造函数的实例,然后通过调用invoke()
方法来获取MainFragment
的实例:
ini
val instance = ::MainFragment
注意这里的instace
是函数类型 KFunction0<MainFragment>
的引用,我们要想获取MainFragment
的实例,还得调用该函数类型的invoke()
方法。
ini
val instance = ::MainFragment.invoke()
虽然这种写法看上去有些多余,但是我们也要了解一下使用这种获取构造函数引用的方式来创建一个对象。
总结
关于 ::
运算符的应用到这里就介绍完了。这种通过获取属性、方法、构造函数引用的方式虽然看上去不太好理解。但是在实际开发中我们常常还是会遇到,所以我们还是要去理解这种方式。到这里关于Kotlin
中基础知识的介绍也算是结尾了。我们下期再见~