Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unexpected behaviour of override with Kotlin class delegation

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?

like image 636
IARI Avatar asked Mar 07 '23 18:03

IARI


1 Answers

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); }
}
like image 178
guenhter Avatar answered Mar 23 '23 15:03

guenhter