Reflection 科特林:代表怎么样;是否访问了get和setValue方法?

Reflection 科特林:代表怎么样;是否访问了get和setValue方法?,reflection,delegates,kotlin,Reflection,Delegates,Kotlin,我一直想知道(“by”-关键字)在后台是如何工作的。我知道,根据约定,委托(by的右侧)必须实现get和setValue(…)方法,但是编译器如何确保这一点,以及如何在运行时访问这些方法呢?我最初的想法是,显然委托人必须让我实现某种“SuperDelegate”接口,但情况似乎并非如此。所以剩下的唯一选项(我知道)是使用反射来访问这些方法,可能在语言本身的低层实现。我觉得这有点奇怪,因为根据我的理解,这是相当低效的。而且反射API甚至不是stdlib的一部分,这使得它更加奇怪 我假设后者已经是答


我一直想知道(“by”-关键字)在后台是如何工作的。
我知道,根据约定,委托(by的右侧)必须实现get和setValue(…)方法,但是编译器如何确保这一点,以及如何在运行时访问这些方法呢?
我最初的想法是,显然委托人必须让我实现某种“SuperDelegate”接口,但情况似乎并非如此。
所以剩下的唯一选项(我知道)是使用反射来访问这些方法,可能在语言本身的低层实现。我觉得这有点奇怪,因为根据我的理解,这是相当低效的。而且反射API甚至不是stdlib的一部分,这使得它更加奇怪

我假设后者已经是答案的一部分。因此,让我进一步问您以下问题:为什么没有SuperDelegate接口来声明我们被迫使用的getter和setter方法?那不是更干净吗?

以下不是问题的关键


所描述的接口甚至已经在和中定义。要决定使用哪一个,可以根据我们是否有val/var来确定。甚至可以忽略这一点,因为编译器阻止在val上调用setValue方法,并且只使用ReadWriteProperty接口作为超级委托


可以说,当要求委托实现某个接口时,构造的灵活性会降低。尽管这是假设用作委托的类可能不知道被用作委托,但考虑到对必要方法的特定要求,我发现这是不可能的。如果你仍然坚持,这里有一个疯狂的想法:为什么不让该类通过实现所需的接口(我知道目前还不可能,但见鬼,为什么不?可能有一个很好的“为什么不”,请让我知道作为旁注)。

如果你看一下生成的Kotlin字节码,您将看到在包含您正在使用的委托的类中创建了一个私有字段,属性的
get
set
方法只需调用该委托字段上的相应方法

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

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

让我们看一看为具有委托属性的类生成的字节码(您可以使用内置IntelliJ IDEA实现这一点):

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

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

    // access flags 0x12
    private final Lkotlin/Lazy; x$delegate
    
  • 这是构造函数(
    )的一部分,初始化委托字段,将函数传递给
    惰性
    构造函数:

    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()
    的代码:

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

这种约定并不是编译器端实现的唯一约定:还有
迭代器
比较
调用
和其他可以重载的运算符——它们都是类似的,但它们的代码生成逻辑比委托的代码生成逻辑简单

但是,请注意,它们都不需要实现接口:可以为未实现
可比
的类型定义
compareTo
运算符,并且
迭代器()
不要求该类型是
可比
的实现,它们在编译时无论如何都会被解析


虽然接口方法可能比运算符惯例更简洁,但它允许的灵活性较低:例如,无法使用接口方法,因为它们无法编译为覆盖接口方法的方法。

谢谢您的回答。大部分内容涵盖了我的问题。“没有这样的接口,因为它是不需要的(对于运行时性能)”-有道理。我仍然在想,为什么你不让代理实现这个接口呢。在我看来,这样更容易理解和创建代表。但我愿意把它放在那里:)(如果没有什么惊人的事情发生,我会接受一点)“扩展函数不允许方法重写”-让我明白了这一点。
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;
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