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

2

11


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).

Berkie answered 8/3, 2017 at 10:57 Comment(0)
H
13

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.

Horvath answered 8/3, 2017 at 11:15 Comment(2)
Thanks for the answer. Covers my question for the most part. "There is no such interface since it is not needed (for runtime performance)" - makes sense. I am still wondering tho why you don't just have delegates implement the interface anyway. It would arguably be easier to understand and create delegates then, in my opinion. But I am willing to leave it there :) (will accept in a bit if nothing amazing comes along)Berkie
"Extension Functions do not allow method overriding" - got me on that one.Berkie
D
3

If you look at the generated Kotlin bytecode, you'll see that a private field is created in the class holding the delegate you're using, and the get and set method for the property just call the corresponding method on that delegate field.

As the class of the delegate is known at compile time, no reflection has to happen, just simple method calls.

Declarer answered 8/3, 2017 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.