The problem lies in the generated code:
public final class BaseImpl implements Base {
@NotNull
private final String message;
private final int x;
@NotNull
public String getMessage() {
return this.message;
}
public void print() {
String var1 = this.getMessage();
System.out.println(var1);
}
public final int getX() {
return this.x;
}
public BaseImpl(int x) {
this.x = x;
this.message = "BaseImpl: x = " + this.x;
}
}
public interface Base {
@NotNull
String getMessage();
void print();
}
public final class Derived implements Base {
@NotNull
private final String message;
// $FF: synthetic field
private final Base $$delegate_0;
@NotNull
public String getMessage() {
return this.message;
}
public Derived(@NotNull Base b) {
Intrinsics.checkParameterIsNotNull(b, "b");
super();
this.$$delegate_0 = b;
this.message = "Message of Derived";
}
public void print() {
this.$$delegate_0.print();
}
}
It took me a while to spot it, but here's the gist of it:
Delegation doesn't mean extending a specific type; it means overriding the Base (in this case) interface, and sending unoverridden events to the delegated class. Here's two things to pay very close attention to:
public void print() {
this.$$delegate_0.print();
}
And
public final class Derived implements Base {
What does this mean? It means Derived
does in no way override BaseImpl
. To show what I mean, take this code:
interface Base {
val message: String
fun print()
}
open class BaseImpl(val x: Int) : Base { // This needs to be `open` to override
override val message = "BaseImpl: x = $x"
override fun print() { println(message) }
}
class Derived(x: Int) : BaseImpl(x) {
// This property is not accessed from b's implementation of `print`
override val message = "Message of Derived"
}
fun main(args: Array<String>) {
val derived = Derived(10)
derived.print()
}
it will print Message of Derived
. Why? Because now you actually override BaseImpl
. Decompile the code, and remember the two previous things I told you to pay close attention to:
public final class Derived extends BaseImpl {
@NotNull
private final String message = "Message of Derived";
@NotNull
public String getMessage() {
return this.message;
}
public Derived(int x) {
super(x);
}
}
You probably see the print method is gone; this is because you don't override it. Also, it now extends BaseImpl
instead of implements Base
. Any calls to the getMessage
method (which you can see called in the print method) will be "re-directed" to the overridden method, and not the one in the base class.
If you did this in Java, and instead of using the getter called the field directly, it wouldn't work, because you can't override variables in Java. The reason it works in Kotlin out of the box is because, as you see in the decompiled code, it uses methods.
How you want to fix this is up to you. If you still want to use delegated properties, you'll need to override the print method in the Derived
class. Because, as I mentioned earlier, there's no actual relationship between Derived
and BaseImpl
, aside having a shared parent. But Derived
isn't a child of BaseImpl
.
Overriding getMessage()
as EpicPandaForce mentioned would therefore not work. Also, by doing override val message = ...
you do generate an overriding getter, which my second example showed.
TL;DR: Base by b
implements Base, and sends calls to print
to the delegated BaseImpl
. Derived
isn't a child of BaseImpl
, and as a result, BaseImpl
cannot call the getMessage()
getter in Derived
, and therefore prints its own message instead.