使用 Kotlin class 委托覆盖的意外行为

unexpected behaviour of override with Kotlin class delegation

据我了解,class 授权应该

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

Kotlin 支持 Class 委托,请注意 documentation 中的以下语句:

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

考虑到这一点,请考虑以下最小示例:

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"
}

我预计 B(AImpl()).printV() 会打印 B,但它却打印 A,即它使用 AImpl.

的默认实现

此外,如果我使用 super 实现覆盖 B 中的 printV() 方法,即

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

我现在预计 B(AImpl()).printV() 会打印 A,但这次它会打印 B。 这似乎违反直觉。

你能给这个行为一个很好的解释吗?

这按预期工作。

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

总是想象 class 委托,因为您会自己将调用重定向到委托 class:

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

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

这清楚地表明,对 printV 的调用只是委托给 av 的值在 [=50= 中并不重要] 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.

再一次想象一下委托在内部是如何工作的:

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
    }
}

这清楚地表明,a 不再涉及并且 printV 使用您的本地覆盖变量。

更新 1(第二部分的详细说明)

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

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

所以你不能把授权看成是继承。是委托(委托模式查维基百科)

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

所以你接口的所有方法(v-属性和printV)都刚刚生成并转发给委托class。

这里是 class B 的代码片段和反编译的代码,看看它在内部是如何工作的:

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); }
}