kotlin - 当作为参数传递时,属性引用 (::test) 是否等效于访问属性 ({ test }) 的函数,例如 `() -> String` ?

标签 kotlin

我开始怀疑通过 ::test 访问属性是否等同于调用 { test } 或者它是否是使用反射的间接调用。

在查看以下内容时,我想到了这个问题:How can I pass property getter as a function type to another function

虽然 ::test{ test } 都可以工作,但 IDE (Intellij) 将 ::test 设置为 KProperty-type 而后一种类型在分配给变量时是 () -> String。所以这里有区别。但有效的区别是什么?这些是 Java 中的真实方法引用还是访问属性的反射方式?一个变体可能会对另一个变体产生任何性能影响吗?

代码片段:

class Test(val test : String) {
  fun testFun(func: ()->String) : String = func()
  fun callTest() {
    testFun { test } // or (::test) // is it using reflection? are these real references?
  }
}

最佳答案

我认为在这种情况下最好检查字节码以了解发生了什么。

我使用了以下代码:

class Test(val test: String) {
    fun testFun(func: () -> String): Unit = TODO()
    fun callTest() {
        testFun { test }
        testFun(::test)
    }
}

对于 testFun { test } 这是生成的字节码:

ALOAD 0
NEW Test$callTest$1
DUP
ALOAD 0
INVOKESPECIAL Test$callTest$1.<init> (LTest;)V
CHECKCAST kotlin/jvm/functions/Function0
INVOKEVIRTUAL Test.testFun (Lkotlin/jvm/functions/Function0;)V

这是 testFun(::test) 的字节码:

ALOAD 0
NEW Test$callTest$2
DUP
ALOAD 0
CHECKCAST Test
INVOKESPECIAL Test$callTest$2.<init> (LTest;)V
CHECKCAST kotlin/jvm/functions/Function0
INVOKEVIRTUAL Test.testFun (Lkotlin/jvm/functions/Function0;)V

它们看起来几乎完全相同,除了第一个是创建 Test$callTest$1,而第二个是使用 Test$callTest$2。在第二个“版本”中还有一个额外的 CHECKCAST Test,因为 Test$callTest$2 在其构造函数中期望 Test 的实例。

那么,$1$2 有什么区别?

这里是 $1 版本:

final class Test$callTest$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0  {

    ....

    final synthetic LTest; this$0 // reference to your Test instance

    <init>(LTest;)V {
        ALOAD 0
        ALOAD 1
        PUTFIELD Test$callTest$1.this$0 : LTest;
        ALOAD 0
        ICONST_0 // Lambda arity is zero
        INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
        RETURN
    }

    public final String invoke() {
        ALOAD 0
        GETFIELD Test$callTest$1.this$0 : LTest;
        INVOKEVIRTUAL Test.getTest ()Ljava/lang/String;
    }
}

$2:

final class Test$callTest$2 extends kotlin/jvm/internal/PropertyReference0  {

    ...

    <init>(LTest;)V {
        ALOAD 0
        ALOAD 1
        INVOKESPECIAL kotlin/jvm/internal/PropertyReference0.<init> (Ljava/lang/Object;)V
        RETURN
   }

    public Object get() {
        GETFIELD Test$callTest$2.receiver : Ljava/lang/Object;
        CHECKCAST Test
        INVOKEVIRTUAL Test.getTest ()Ljava/lang/String;
        ARETURN
    }
}

所以在字节码指令方面似乎没有太大区别。

编辑: $2 类从其父类 PropertyReference0 继承一个 invoke 方法,该方法调用其 get() 方法,而 >$1 立即声明 invoke。因此,与 $1 相比,如果没有进一步优化,$2 会执行一个额外的方法调用。

关于kotlin - 当作为参数传递时,属性引用 (::test) 是否等效于访问属性 ({ test }) 的函数,例如 `() -> String` ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51842043/

相关文章:

android - IllegalArgumentException : navigation destination xxx is unknown to this NavController

android - 如何查看变化的集合?

android-studio - 有没有办法自动填充参数名称?

android - 不要将Android上下文类放在静态字段中

android - 菜单中的 onOptionsItemSelected 对于使用 actionLayout 的项目是不可点击的

spring-boot - 在 Spring 重新建立RPC连接

java - Android - 创建文件夹时出错

android-studio - Kotlin 文件的 IntelliJ 和 Android Studio 图标

oop - Kotlin 类型推断失败的原因

android - 将广播接收器包装到流中(协程)