The same pattern to delegate properties I found myself inventing at least several times in different code bases. I’m thrilled to share what I found. Let us start the discussion from the very basics and explain how to allow delegate properties with the following syntax:
val property by someObject::anotherProperty
Let’s start from the basics building blocks. There is by
keyword
on Kotlin that helps to delegate interfaces
and properties. These features
will not help to delegate one data
class in another.
Let’s assume we need to have a property that has to delegate to another object’s property. The
basic implementation may look like:
val property: PropertyType
get() {
return base.property
}
Expression function will make it a bit shorter:
val property: PropertyType
get() = base.property
This syntax allows Kotlin compiler to infer the type of the property, so we may simplify the code to the following:
val property
get() = base.property
We may even break Coding Conventions and shorten this a bit more:
val property get() = base.property
That one works pretty well, but let me show a nicer one which is based on the delegated properties:
val property by base::foo
That example will not work as is. We need a specific getValue
operator function to make the example above
work. The documentation on delegated properties
suggests do declare the function:
operator fun <R, T> getValue(thisRef: R, property: KProperty<*>): T
This function can be declared as an extension function
on the type of property references (base::foo
in our case). The type of the
bound callable reference
is KProperty0<R>
where R
is the return type of the property.
It turns out we need the following function to implement the delegation above:
operator fun <R> KProperty0<R>.getValue(x: Any?, property: KProperty<*>): R = this.invoke()
Now the example above will work, and the following would work:
val property by base::foo
The Code
To summarize, the full example is as follows. We may keep that getValue
function somewhere
in the utils to allow more usages.
operator fun <R> KProperty0<R>.getValue(x: Any?, property: KProperty<*>): R {
return this.invoke()
}
class Y(val x: X) {
val boo by x::foo
}
The JVM Bytecode Level
For someone, it is always nice to question about the overhead of the trick above.
Our implementation uses the
bound callable reference
underneath, and it may consume some resources. The easiest way to see that is to use the Kotlin Bytecode
feature in IntelliJ. I’ve been speaking
about that trick last summer.
First we use the Find action… (CMD/CTRL+SHIFT+A
) popup and type the action name:
In the opened Kotlin Bytecode
tool window we may see the generated bytecode for our delegated property code,
it is probably quite hard to understand, so we click the Decompile
button to see the same bytecode as decompiled Java code,
that would be easier to understand:
From the bytecode side we’ll see the following code Java decompiled for the Y
class:
public final class Y {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Y.class), "boo", "getBoo()Ljava/lang/String;"))};
@NotNull private final KProperty0 boo$delegate;
@NotNull private final X x;
@NotNull public final String getBoo() {
return (String)UtilsKt.getValue(this.boo$delegate, this, $$delegatedProperties[0]);
}
public Y(@NotNull X x) {
Intrinsics.checkParameterIsNotNull(x, "x");
super();
this.x = x;
this.boo$delegate = new Y$boo$2(this.x);
}
// ... some code is omitted
}
final class Y$boo$2 extends PropertyReference0 {
@Nullable public Object get() {
return ((X)this.receiver).getFoo();
}
// ... some code is omitted
}
public final class UtilsKt {
public static final Object getValue(@NotNull KProperty0 $this$getValue, @Nullable Object x, @NotNull KProperty property) {
Intrinsics.checkParameterIsNotNull($this$getValue, "$this$getValue");
Intrinsics.checkParameterIsNotNull(property, "property");
return $this$getValue.invoke();
}
// ... some code is omitted
}
From the code we see that the delegated property essentially compiled into the
call to the Y$boo$2
delegate, that simply calls the respective getter of the type X
.
From that we see that the shorter delegation costs us an extra method call. We have to also
pay an extra object with one field of memory per object instanc for that. I’ve made several
experiments with inline
keyword which did not help to evaporate the Y$boo$2
class usage.
val x = ...
// the delegation (it allocates an instance field to store the x::foo class
val boo by x::foo
// the operator that is expected by the `by` expression
operator fun <R> KProperty0<R>.getValue(x: Any?, property: KProperty<*>): R {
return this.invoke()
}