5

When using class delegation in Kotlin, you can override members. However, the reference page on delegation says:

Note, however, that members overridden in this way do not get called from the members of the delegate object, which can only access its own implementations of the interface members.

I want to override a property that is used by methods of the delegate object so that the methods of the delegate object call this overridden property. As the documentation says, overriding the property with the override keyword doesn't accomplish this. Is there a way that I can achieve this behavior? If not, is it a sign that I should be using inheritance instead?

Here is a code example:

interface Base {
    val message: String
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}

class Derived(b: Base) : Base by b {
    // This property is not accessed from b's implementation of `print`
    override val message = "Message of Derived"
}

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print()
}

This code prints "BaseImpl: x = 10", but I want it to print "Message of Derived".

Wren
  • 123
  • 2
  • 4

2 Answers2

9

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.

Zoe
  • 27,060
  • 21
  • 118
  • 148
1
interface BaseInterface {
    fun doSomething()
    fun doSomethingElse()
}

class BaseImpl : BaseInterface {
    override fun doSomething() = println("doSomething in BaseImpl")
    override fun doSomethingElse() = println("doSomethingElse in BaseImpl")
}

class Derived : BaseInterface by BaseImpl() {
    override fun doSomething() = println("doSomething in Derived")
}

fun main() {
    val obj = Derived()
    obj.doSomething()
    obj.doSomethingElse()
}

doSomething in Derived

doSomethingElse in BaseImpl

Vairavan
  • 1,246
  • 1
  • 15
  • 19