unexpected behaviour of override with Kotlin class delegation
Asked Answered
B

1

5

From what I understand, class delegation is supposed to

allow object composition to achieve the same code reuse as inheritance. [wikipedia]

Kotlin supports Class Delegation, and note the following statement form the documentation:

overrides work as you might expect: The compiler will use your override implementations instead of those in the delegate object.

With this in mind, consider the following minimal example:

interface A {
  val v: String

  fun printV() {
    Logger.getLogger().info(Logger.APP, "A", v)
  }
}

class AImpl : A {
  override val v = "A"
}

class B(a: A) : A by a {
  override val v: String = "B"
}

I expected that B(AImpl()).printV() would print B, however instead it prints A, i.e. it uses the default implementation of AImpl.

Moreover, if I override the printV() Method in B using the super implementation, i.e.

class B(a: A) : A by a {
  override val v: String = "B"
  override fun printV() {
    super.printV()
  }
}

I now expected that B(AImpl()).printV() would print A, however this time it prints B. This seems counterintuitive.

Can you give a good explanation for this behavior?

Bighead answered 23/10, 2017 at 9:23 Comment(0)
G
8

This works as expected.

I expected that B(AImpl()).printV() would print B, however instead it prints A, i.e. it uses the default implementation of AImpl.

Always imagine class delegation, as you would redirect the call to the delegation class by yourself:

class B(private val a: A) : A {
    override val v: String = "B"

    override fun printV() {
        a.printV()
    }
}

This makes clear, that a call to printV is just delegated to a and it doesn't really matter what the value of v is in the class B.


Moreover, if I override the printV() Method in B using the super implementation, i.e. I now expected that B(AImpl()).printV() would print A, however this time it prints B. This seems counterintuitive.

Again, imagine how the delegation works internally:

class B(private val a: A) : A {
    override val v: String = "B"

    override fun printV() {
        super.printV() // the super call than uses the overridden v
    }
}

This makes clear, that a is not longer involved and printV uses your local overridden variable.

Update 1 (elaboration on second part)

https://kotlinlang.org/docs/reference/delegation.html

The Delegation pattern has proven to be a good alternative to implementation inheritance

So you must not see delegation as inheritance. It is delegation (look up wikipedia for the delegation pattern)

... and the compiler will generate all the methods of Base that forward to b.

So all the methods of your interface (v-property and printV) are just generated and forwarded to the delegation class.

Here the code snippets of the class B and the decompiled code to see how it works internally:

class B(a: A) : A by a {
    override val v: String = "B"
}
class C(a: A) : A by a {
    override val v: String = "B"
    override fun printV() {
        super.printV()
    }
}

public final class B implements A {
  @NotNull
  private final String v = "B";

  public B(@NotNull A a) {
    this.$$delegate_0 = a;
    this.v = "B"; 
  } 

  @NotNull
  public String getV() { return this.v; }

  public void printV() {
    this.$$delegate_0.printV();
  }
}

public final class C implements A {
  @NotNull
  private final String v = "B";

  public C(@NotNull A a) {
    this.$$delegate_0 = a;
  }

  @NotNull
  public String getV() {
    return this.v;
  }

  /*This more or less means super.printV() */
  public void printV() { A.DefaultImpls.printV(this); }
}
Goby answered 23/10, 2017 at 9:35 Comment(2)
You finished the answer after I just finish my first two sentence. =p If you use this::class instead of v, the first one will be AImpl and the second one will be BKenric
I'm still unsure, If I understand it properly - I would have expected the delegation to be more inheritance-like. Could you elaborate on the second part regarding super a bit more?Bighead

© 2022 - 2024 — McMap. All rights reserved.