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

时间:2017-03-08 10:57:08

标签: reflection delegates kotlin

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

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

以下问题不是必不可少的

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

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

2 个答案:

答案 0 :(得分:11)

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

让我们看看为具有委托属性的类生成的字节码(您可以使用IntelliJ IDEA中内置的the bytecode viewing tool来执行此操作):

class C {
    val x by lazy { 123 }
}

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

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

    // access flags 0x12
    private final Lkotlin/Lazy; x$delegate
    
  • 这是初始化委托字段的构造函数(<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;
    
  • 这是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
    

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

此约定不是在编译器端实现的唯一一个:还有iteratorcompareToinvoke以及可以重载的其他运算符 - 所有这些都是类似,但它们的代码生成逻辑比委托更简单。

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

虽然接口方法可能比运算符约定更简洁,但它会降低灵活性:例如,extension functions无法使用,因为它们无法编译为覆盖接口的方法。

答案 1 :(得分:3)

如果查看生成的Kotlin字节码,您将看到在包含您正在使用的委托的类中创建了私有字段,并且该属性的getset方法只需在该委托字段上调用相应的方法。

由于委托的类在编译时是已知的,因此不需要进行任何反射,只需简单的方法调用。