Kotlin语法基础篇十五::: 运算符

前言

在上一篇文章中我们介绍了Kotlin中的集合的使用,这篇文章我们来介绍Kotlin中反射的应用。

1.获取类引用

相信使用Kotlin开发Android的读者,对于下面这行代码都会很熟悉:

ini 复制代码
val TAG  = MainActivity::class.simpleName

我们知道反射其实是用来获取类运行时的信息,它的语法结构看上去也比较简单:

arduino 复制代码
ClassName::class

在类名后添加::class这意味着我们获取指定类的类引用,在Kotlin中该引用是KClass类型的值。如果要获取Java的类引用,我们需要在KClass示例上加上.java属性。KClassKotlin中为我们提供的一个获取运行时类信息的类,该类包含了我们在运行时所需要的各种属性,如下代码示例:

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属性就是通过JavaClass对象的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中基础知识的介绍也算是结尾了。我们下期再见~

相关推荐
工程师老罗1 小时前
如何在Android工程中配置NDK版本
android
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端