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中基础知识的介绍也算是结尾了。我们下期再见~

相关推荐
腾讯TNTWeb前端团队15 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom5 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom5 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom5 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom5 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试