Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin: How are a Delegate's get- and setValue Methods accessed?


I've been wondering how delegated properties ("by"-Keyword) work under-the-hood.
I get that by contract the delegate (right side of "by") has to implement a get and setValue(...) method, but how can that be ensured by the compiler and how can those methods be accessed at runtime?
My initial thought was that obviously the delegates must me implementing some sort of "SuperDelegate"-Interface, but it appears that is not the case.
So the only option left (that I am aware of) would be to use Reflection to access those methods, possibly implemented at a low level inside the language itself. I find that to be somewhat weird, since by my understanding that would be rather inefficient. Also the Reflection API is not even part of the stdlib, which makes it even weirder.

I am assuming that the latter is already (part of) the answer. So let me furthermore ask you the following: Why is there no SuperDelegate-Interface that declare the getter and setter methods that we are forced to use anyway? Wouldn't that be much cleaner?

The following is not essential to the question


The described Interface(s) are even already defined in ReadOnlyProperty and ReadWriteProperty. To decide which one to use could then be made dependable on whether we have a val/var. Or even omit that since calling the setValue Method on val's is being prevented by the compiler and only use the ReadWriteProperty-Interface as the SuperDelegate.

Arguably when requiring a delegate to implement a certain interface the construct would be less flexible. Though that would be assuming that the Class used as a Delegate is possibly unaware of being used as such, which I find to be unlikely given the specific requirements for the necessary methods. And if you still insist, here's a crazy thought: Why not even go as far as to make that class implement the required interface via Extension (I'm aware that's not possible as of now, but heck, why not? Probably there's a good 'why not', please let me know as a side-note).

like image 716
Lukas Avatar asked Mar 08 '17 10:03

Lukas


People also ask

What is get () in Kotlin?

In Kotlin, setter is used to set the value of any variable and getter is used to get the value. Getters and Setters are auto-generated in the code. Let's define a property 'name', in a class, 'Company'. The data type of 'name' is String and we shall initialize it with some default value.

How does Kotlin delegate work?

So a delegate is just a class with two methods: for getting and setting value of a property. To give it some more information, it is provided with the property it's working with via the instance of KProperty class, and an object that has this property via thisRef . That's it!

What are delegated properties?

Simply put, delegated properties are not backed by a class field and delegate getting and setting to another piece of code. This allows for delegated functionality to be abstracted out and shared between multiple similar properties – e.g. storing property values in a map instead of separate fields.

What is the difference between field and property in Kotlin?

However, to avoid confusion, I prefer to define Fields and Properties separately based on the following: Fields are private member variables of a class. Memory is allocated. Properties are public or protected getter or setter functions which allow you to access to the private fields.


1 Answers

The delegates convention (getValue + setValue) is implemented at the compiler side and basically none of its resolution logic is executed at runtime: the calls to the corresponding methods of a delegate object are placed directly in the generated bytecode.

Let's take a look at the bytecode generated for a class with a delegated property (you can do that with the bytecode viewing tool built into IntelliJ IDEA):

class C {
    val x by lazy { 123 }
}

We can find the following in the generated bytecode:

  • This is the field of the class C that stores the reference to the delegate object:

    // access flags 0x12
    private final Lkotlin/Lazy; x$delegate
    
  • This is the part of the constructor (<init>) that initialized the delegate field, passing the function to the Lazy constructor:

    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;
    
  • And this is the code of 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
    

    You can see the call to the getValue method of Lazy that is placed directly in the bytecode. In fact, the compiler resolves the method with the correct signature for the delegate convention and generates the getter that calls that method.

This convention is not the only one implemented at the compiler side: there are also iterator, compareTo, invoke and the other operators that can be overloaded -- all of them are similar, but the code generation logic for them is simpler than that of delegates.

Note, however, that none of them requires an interface to be implemented: the compareTo operator can be defined for a type not implementing Comparable<T>, and iterator() does not require the type to be an implementation of Iterable<T>, they are anyway resolved at compile-time.

While the interfaces approach could be cleaner than the operators convention, it would allow less flexibility: for example, extension functions could not be used because they cannot be compiled into methods overriding those of an interface.

like image 160
hotkey Avatar answered Oct 16 '22 14:10

hotkey