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

时间:2018-08-14 12:57:37

标签: kotlin

我开始怀疑是通过::test访问属性是否等效于调用{ test }还是使用反射进行的间接调用。

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

虽然::test{ test }都可以工作,但是IDE(Intellij)将::test设置为KProperty类型,而当() -> String类型为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? } } 时分配给变量。所以这里有区别。但是有效的区别是什么?这些真正的方法引用是Java中的引用,还是它们作为访问属性的一种反映方式?一种变体可能会对另一种变体产生性能影响吗?

代码段:

.cta-43274891247129739-info {
  position: absolute;
  left: 0;
  top: 50px;
  margin: 10px 10px;
  animation: cta-43274891247129739 4s 1s both ease-out;
  text-align: center;
}
@keyframes cta-43274891247129739 {
  0% {
    transform: translateY(1em);
    opacity: 0;
  }
  16.6667%, 83.3333% {
    opacity: 1;
    transform: translateY(0em);
  }
  100% {
    transform: translateY(-40px);
  }
}

1 个答案:

答案 0 :(得分:7)

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

我使用了以下代码:

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继承自其父类invoke的{​​{1}}方法,该方法调用其PropertyReference0方法,而get()立即声明$1。因此,与invoke相比,$2无需执行进一步的优化。