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



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

在查看以下内容时,我想到了这个问题:如何将属性getter作为函数类型传递给另一个函数

虽然::test{ test }都有效,但 IDE (Intellij( 将::test设置为KProperty-类型,而后一种类型在分配给变量时() -> 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从调用其get()方法的父类PropertyReference0继承invoke方法,而$1立即声明invoke。因此,与$1相比,无需进一步优化$2执行一个额外的方法调用。

相关内容