使用类委托时,是否有办法覆盖委托对象的方法使用的成员?

Gar*_*ett 6 kotlin

在 Kotlin 中使用类委托时,您可以覆盖成员。然而,关于授权的参考页说:

但请注意,以这种方式重写的成员不会从委托对象的成员中调用,委托对象只能访问其自己的接口成员实现。

我想重写委托对象的方法使用的属性,以便委托对象的方法调用此重写的属性。正如文档所述,使用关键字覆盖属性并override不能实现此目的。有没有办法可以实现这种行为?如果不是,这是否表明我应该使用继承?

这是一个代码示例:

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()
}
Run Code Online (Sandbox Code Playgroud)

此代码打印“BaseImpl:x = 10”,但我希望它打印“Message of Derived”。

Zoe*_*Zoe 11

问题出在生成的代码上:

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();
   }
}
Run Code Online (Sandbox Code Playgroud)

我花了一段时间才发现它,但要点如下:

委托并不意味着扩展特定类型;而是意味着扩展特定类型。这意味着重写 Base(在本例中)接口,并将未重写的事件发送到委托类。这里有两件事需要密切关注:

public void print() {
   this.$$delegate_0.print();
}
Run Code Online (Sandbox Code Playgroud)

public final class Derived implements Base {
Run Code Online (Sandbox Code Playgroud)

这是什么意思?这意味着Derived绝不覆盖BaseImpl。为了说明我的意思,请使用以下代码:

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()
}
Run Code Online (Sandbox Code Playgroud)

它将打印Message of Derived。为什么?因为现在你实际上覆盖了BaseImpl. 反编译代码,记住我之前告诉你要密切注意的两件事:

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);
   }
}
Run Code Online (Sandbox Code Playgroud)

您可能会看到 print 方法消失了;这是因为你没有覆盖它。另外,它现在extends BaseImpl代替了implements Base. 对该getMessage方法的任何调用(您可以在 print 方法中看到调用)都将“重定向”到重写的方法,而不是基类中的方法。

如果您在 Java 中执行此操作,而不是使用直接调用字段的 getter,则它将不起作用,因为您无法覆盖 Java 中的变量。它在 Kotlin 中开箱即用的原因是,正如您在反编译代码中看到的那样,它使用方法。

您想如何解决这个问题取决于您。如果您仍然想使用委托属性,则需要重写类中的 print 方法DerivedDerived因为,正如我之前提到的,除了有一个共同的父级之外,和之间没有实际的关系BaseImpl。但Derived不是 的孩子BaseImpl

getMessage()因此,正如EpicPandaForce提到的那样,覆盖是行不通的。另外,通过这样做,override val message = ...您确实会生成一个压倒一切的 getter,正如我的第二个示例所示。

TL;DR: Base by b实现 Base,并将调用发送print到委托的BaseImpl. Derived不是 的子级BaseImpl,因此BaseImpl无法调用getMessage()中的 getter Derived,因此改为打印其自己的消息。