Kotlin: ¿Cómo se accede a los methods get y setValue de un delegado?

Me he estado preguntando cómo las properties delegadas ("por" -palabra key) funcionan bajo el capó.
Lo entiendo por contrato el delegado (lado derecho de "por") tiene que implementar un método get y setValue (…), pero ¿cómo puede garantizarlo el comstackdor y cómo se puede acceder a esos methods en time de ejecución?
Mi primer pensamiento fue que, obviamente, los delegates deben implementar algún tipo de "SuperDelegado" -Interfaz, pero parece que no es el caso.
Entonces, la única opción que queda (de la que soy consciente) sería utilizar Reflection para acceder a esos methods, posiblemente implementados en un nivel bajo dentro del lenguaje mismo. Encuentro que es algo extraño, ya que, según entiendo, sería bastante ineficiente. Además, Reflection API ni siquiera es parte de stdlib, lo que lo hace aún más extraño.

Estoy asumiendo que este último ya es (parte de) la respuesta. Así que permítanme además preguntarle lo siguiente: ¿Por qué no hay una interfaz SuperDelegate que declare los methods getter y setter que estamos obligados a usar de todos modos? ¿No sería eso mucho más limpio?

Lo siguiente no es esencial para la pregunta


Las interfaces descritas ya están definidas en ReadOnlyProperty y ReadWriteProperty . Decidir cuál usar podría ser confiable si tenemos un val / var. O incluso omita eso ya que el comstackdor está evitando llamar al método setValue en val y solo usa ReadWriteProperty-Interface como SuperDelegate.

Podría decirse que cuando se requiere que un delegado implemente una determinada interfaz, la construcción sería less flexible. Sin embargo, eso supondría que la Clase utilizada como Delegado posiblemente no esté consciente de ser utilizada como tal, lo que me parece poco probable dados los requisitos específicos para los methods necesarios. Y si todavía insistes, aquí está una locura: ¿por qué no llegar a hacer que la class implemente la interfaz requerida a través de la extensión (soy consciente de que no es posible a partir de ahora, pero diablos, ¿por qué no? Probablemente haya una buena '¿por qué no?', por favor, hágamelo saber como nota al margen).

La convención de delegates ( getValue + setValue ) se implementa en el lado del comstackdor y básicamente ninguna de sus lógicas de resolución se ejecuta en time de ejecución: las llamadas a los methods correspondientes de un object delegado se colocan directamente en el bytecode generado.

Echemos un vistazo al bytecode generado para una class con una propiedad delegada (puede hacerlo con la herramienta de visualización de códigos de bytes integrada en IntelliJ IDEA):

 class C { val x by lazy { 123 } } 

Podemos encontrar lo siguiente en el bytecode generado:

  • Este es el campo de la class C que almacena la reference al object delegado:

     // access flags 0x12 private final Lkotlin/Lazy; x$delegate 
  • Esta es la parte del constructor ( <init> ) que inicializó el campo de delegado, pasando la function al constructor 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; 
  • Y este es el código de 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 

    Puede ver la llamada al método getValue de Lazy que se coloca directamente en el bytecode. De hecho, el comstackdor resuelve el método con la firma correcta para la convención de delegado y genera el getter que llama a ese método.

Esta convención no es la única implementada en el lado del comstackdor: también hay iterator , compareTo , invoke y otros operadores que pueden estar sobrecargados; todos son similares, pero la lógica de generación de código para ellos es más simple que la de los delegates. .

Sin embargo, tenga en count que ninguno de ellos requiere la implementación de una interfaz : el operador compareTo puede definirse para un tipo que no implementa Comparable<T> , y el iterator() no requiere que el tipo sea una implementación de Iterable<T> , de todos modos, se resuelven en time de compilation.

Si bien el enfoque de las interfaces podría ser más limpio que la convención de los operadores, permitiría una menor flexibilidad: por ejemplo, las funciones de extensión no se podrían usar porque no se pueden comstackr en methods que anulen los de una interfaz.

Si miras el bytecode de Kotlin generado, verás que se crea un campo privado en la class que contiene el delegado que estás usando, y el método get y set para la propiedad solo llama al método correspondiente en ese campo de delegado.

Como la class del delegado se conoce en time de compilation, no tiene que ocurrir reflexión, solo llamadas a methods simples.

  • Manera idiomática de invocar methods a través de la reflexión en Kotlin
  • Kotlin enlazó la incoinheritance de las references invocables
  • ¿Cómo get una KClass de Array?
  • Implementando una interfaz de kotlin en java
  • Kotlin invoke getter / setter reflexivamente
  • Kotlin: isAssignableFrom y reflexiones del tipo de reflexión
  • Crear una nueva instancia de una KClass
  • ¿Cómo puedo crear una instancia de un object usando valores de parameters de constructor pnetworkingeterminados en Kotlin?