我一直想知道delegated properties(“by -Keyword”)是如何在幕后工作的。我通过合同获得了代表(“by”的右侧) )必须实现一个get和setValue(...)方法,但是如何通过编译器确保这些方法以及如何在运行时访问这些方法?
我最初的想法是,显然代表们必须实现一些一种“SuperDelegate” - 接口,但似乎并非如此。
所以剩下的唯一选择(我所知道的)就是使用Reflection来访问那些方法,可能是在内部实现的语言本身。我发现这有些奇怪,因为根据我的理解,这将是相当低效的。此外,Reflection API甚至不是stdlib的一部分,这使得它更加怪异。
我假设后者已经(部分)答案了。所以让我进一步问你以下内容:为什么没有SuperDelegate-Interface声明我们被迫使用的getter和setter方法呢?那不是更干净吗?
以下问题不是必不可少的
所描述的接口甚至已在ReadOnlyProperty和ReadWriteProperty中定义。然后决定使用哪一个可以依赖于我们是否有val / var。或者甚至省略,因为编译器阻止在val上调用setValue方法,并且只使用ReadWriteProperty-Interface作为SuperDelegate。
当需要委托实现某个接口时,该构造的灵活性会降低。虽然这可以假设用作代表的类可能没有意识到这样使用,但鉴于对必要方法的具体要求,我认为不太可能。如果你仍然坚持,这里有一个疯狂的想法:为什么甚至不能通过Extension使该类实现所需的接口(我知道现在不可能,但是,为什么不呢?可能有一个很好的'为什么不',请让我知道作为附注)。
答案 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。
此约定不是在编译器端实现的唯一一个:还有iterator
,compareTo
,invoke
以及可以重载的其他运算符 - 所有这些都是类似,但它们的代码生成逻辑比委托更简单。
但请注意,它们都不需要实现接口:可以为未实现compareTo
的类型定义Comparable<T>
运算符,并{{1不要求类型是iterator()
的实现,它们无论如何都要在编译时解析。
虽然接口方法可能比运算符约定更简洁,但它会降低灵活性:例如,extension functions无法使用,因为它们无法编译为覆盖接口的方法。
答案 1 :(得分:3)
如果查看生成的Kotlin字节码,您将看到在包含您正在使用的委托的类中创建了私有字段,并且该属性的get
和set
方法只需在该委托字段上调用相应的方法。
由于委托的类在编译时是已知的,因此不需要进行任何反射,只需简单的方法调用。