Kotlin:如何访问Delegate的get-和setValue方法?

Luk*_*kas 10 reflection delegates kotlin


我一直在想如何委托属性("by -Keyword")如何工作.
我通过契约获得了委托("by"的右侧)必须实现get和setValue(...)方法,但是如何通过编译器确保这一点以及如何在运行时访问这些方法?
我最初的想法是,代表们显然必须实现某种"SuperDelegate" - 接口,但似乎并非如此.
所以剩下的唯一选择(我所知道的)就是使用Reflection来访问那些方法,这些方法可能在语言本身的低层实现.我发现这有些奇怪,因为根据我的理解,这将是相当低效的.此外,Reflection API甚至不是stdlib的一部分,这使得它更加怪异.

我假设后者已经(部分)答案了.所以让我进一步问你以下内容:为什么没有SuperDelegate-Interface声明我们被迫使用的getter和setter方法呢?那不是更干净吗?

以下问题不是必不可少的


所描述的接口甚至已经在ReadOnlyPropertyReadWriteProperty中定义.然后决定使用哪一个可以依赖于我们是否有val/var.或者甚至省略,因为编译器阻止在val上调用setValue方法,并且只使用ReadWriteProperty-Interface作为SuperDelegate.

可以说,当要求委托实现某个接口时,该构造的灵活性会降低.虽然这可以假设用作代表的类可能没有意识到这样使用,但鉴于对必要方法的具体要求,我认为不太可能.如果你仍然坚持,这里有一个疯狂的想法:为什么不通过扩展使该类实现所需的接口(我知道现在不可能,但是,为什么不呢?可能有一个好的'为什么不',请作为旁注告诉我.

hot*_*key 11

委托约定(getValue+ setValue)在编译器端实现,基本上没有一个解析逻辑在运行时执行:对委托对象的相应方法的调用直接放在生成的字节码中.

让我们看看为具有委托属性的类生成的字节码(您可以使用IntelliJ IDEA中内置的字节码查看工具):

class C {
    val x by lazy { 123 }
}
Run Code Online (Sandbox Code Playgroud)

我们可以在生成的字节码中找到以下内容:

  • 这是C存储对委托对象的引用的类的字段:

    // access flags 0x12
    private final Lkotlin/Lazy; x$delegate
    
    Run Code Online (Sandbox Code Playgroud)
  • 这是构造函数(<init>)的一部分,它初始化了委托字段,将函数传递给Lazy构造函数:

    ALOAD 0
    GETSTATIC C$x$2.INSTANCE : LC$x$2;
    CHECKCAST kotlin/jvm/functions/Function0
    INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
    PUTFIELD C.x$delegate : Lkotlin/Lazy;
    
    Run Code Online (Sandbox Code Playgroud)
  • 这是代码getX():

    L0
      ALOAD 0
      GETFIELD C.x$delegate : Lkotlin/Lazy;
      ASTORE 1
      ALOAD 0
      ASTORE 2
      GETSTATIC C.$$delegatedProperties : [Lkotlin/reflect/KProperty;
      ICONST_0
      AALOAD
      ASTORE 3
    L1
      ALOAD 1
      INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object;
    L2
      CHECKCAST java/lang/Number
      INVOKEVIRTUAL java/lang/Number.intValue ()I
      IRETURN
    
    Run Code Online (Sandbox Code Playgroud)

    您可以看到对getValue方法的调用Lazy直接放在字节码中.实际上,编译器使用委托约定的正确签名解析方法,并生成调用该方法的getter.

该公约是不是在编译器侧实现的只有一个:也有iterator,compareTo,invoke和可以被重载的其他运营商-所有这些都是相似的,但对他们的生成逻辑比代表更简单.

但是,请注意,它们都不需要实现接口:compareTo可以为未实现的类型定义运算符Comparable<T>,并且iterator()不要求类型是实现Iterable<T>,它们无论如何都在编译时解析.

虽然接口方法可能比运算符约定更简洁,但它允许更少的灵活性:例如,无法使用扩展函数,因为它们无法编译为覆盖接口的方法.