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

我一直想知道如何委托属性 (“由”的关键字)的工作。
我通过契约得到了委托(“by”的右边)必须实现一个get和setValue(…)方法,但是怎样才能确保编译器以及如何在运行时访问这些方法呢?
我最初的想法是,很明显,代表们必须实施某种“SuperDelegate” – 接口,但似乎并非如此。
所以剩下的唯一选择就是使用Reflection来访问这些方法,可能在语言本身的低层实现。 我觉得有些奇怪,因为根据我的理解,这将是相当低效的。 此外,反射API甚至不是stdlib的一部分,这使得它更怪。

我假设后者已经是(部分)答案。 所以让我再问你下面的问题:为什么没有声明getter和setter方法的SuperDelegate-Interface? 那不会更清洁吗?

以下对于这个问题并不重要


所描述的接口甚至已经在ReadOnlyProperty和ReadWriteProperty中定义了。 要决定使用哪一个可以取决于我们是否有val / var。 或者甚至省略,因为在val上调用setValue方法被编译器阻止,只能使用ReadWriteProperty接口作为SuperDelegate。

可以说,当要求代表实现某个接口的时候,构造会变得不那么灵活。 虽然这会假定作为代表使用的类别可能不知道这样使用,但是由于必要的方法的具体要求,我认为这是不太可能的。 如果你仍然坚持,这里有一个疯狂的想法:为什么不甚至使这个类通过扩展实现所需的接口(我知道这是不可能的,但现在,但是,为什么不呢?可能有一个好的'为什么不',请让我知道作为一个旁注)。

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

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

 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 Cx$delegate : Lkotlin/Lazy; 
  • 这是getX()的代码:

     L0 ALOAD 0 GETFIELD Cx$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 

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

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

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

虽然接口方法可能比操作符约定更清晰,但它会减少灵活性:例如, 扩展函数不能被使用,因为它们不能被编译成覆盖接口的方法。

如果您查看生成的Kotlin字节码,您将看到在持有所使用的委托的类中创建了一个私有字段,该属性的getset方法只是在该委托字段上调用相应的方法。

由于委托类在编译时是已知的,所以不需要反射,只需简单的方法调用即可。