【问题标题】:Trait mixin with conflicting members: Why my code can be compiled successfully?具有冲突成员的特征混合:为什么我的代码可以成功编译?
【发布时间】:2022-02-02 15:47:39
【问题描述】:

对不起,这个问题可能有点长,因为我想尽可能准确地描述问题和我的理解。

最近在学习 Scala 的特征系统。

我做了一些关于冲突成员的实验,见以下代码:

trait TA {
  def play() = println("TA play")
}

trait TB {
  def play() = println("TB play")
}

trait TC {
  def play() = println("TC play")
}

class MyClass extends TA with TB with TC {
}

当然,这段代码编译失败了:

Error:(13, 8) class MyClass inherits conflicting members:
  method play in trait TB of type ()Unit  and
  method play in trait TC of type ()Unit
(Note: this can be resolved by declaring an override in class MyClass.)
class MyClass extends TA with TB with TC {
      ^

我的理解是这样的

MyClass的线性化是{MyClass, TC, TB, TA},但是由于TBplayTCplay上没有override,所以编译失败。

一种解决方法是在TATBTC 上标记override,如下所示:

trait IPlay {
  def play()
}

trait TA extends IPlay {
  override def play() = println("TA play")
}

trait TB extends IPlay {
  override def play() = println("TB play")
}

trait TC extends IPlay {
  override def play() = println("TC play")
}

class MyClass extends TA with TB with TC {
}

好的,这段代码可以按预期编译成功。

但是这个呢:

trait TA {
  def play() = println("TA play")
}

trait TB {
  def play() = println("TB play")
}

trait TC {
  def play() = println("TC play")
}

class MyClass extends TA with TB with TC {
  override def play(): Unit = {
    println("MyClass play")
    super.play()
    super[TC].play()
    super[TA].play()
    super[TB].play()
  }
}

(new MyClass).play()

// -- Output:
// MyClass play
//   TC play
//   TC play
//   TA play
//   TB play

让我吃惊的是,这段代码也能编译成功。

MyClass的线性化仍然是{MyClass, TC, TB, TA},并且TBplayTCplay上没有override,唯一的区别是我添加了一个@ 987654343@ on play in MyClass

为什么能编译成功?

请注意,Scala 不是 Java 或 C#,没有冲突成员的默认行为。

在 Java 中,如果您不将方法标记为 @Override,则默认行为是覆盖。

在 C# 中,如果您不将方法标记为 override,则默认行为是隐藏。

在 Scala 中,如果您不将方法标记为 override,则没有默认行为,它们应该是冲突的成员,IMO。

所以我觉得上面的代码编译应该是失败的,但是为什么能成功呢?

或者我的理解是错误的。

非常感谢。

【问题讨论】:

  • 您在最终类中显式覆盖并精心挑选要调用的实现。固然这是合法的。这就是 mixin 的工作原理。
  • @texasbruce 但我只是在最后一个类中重写,TBTBTC 中的 play 方法仍然存在冲突。也就是说,在mixin之后,TATBTC中的play方法的语义是什么?它们是overridehideundefined

标签: scala oop


【解决方案1】:

太看不出潜在的冲突从何而来,我们需要采用MyClass的“观点”。

让我们回顾一下你的例子,看看为什么你会得到这些结果(为了简化,我将使用 2 个特征而不是 3 个)。

第一个例子:通常的冲突

trait TA { def play() = println("TA") }
trait TB { def play() = println("TB") }
class MyClass extends TA with TB {}

这失败了,因为这两个特征都定义了具有相同签名的 play 方法,但尚不清楚为 MyClass.play 选择哪一个。有人可能会争辩说“编译器可以选择 TB.play,因为它是最后一个混合特征”,但由于没有声明覆盖某些内容的 intent,Scala 拒绝了它。原则上是可行的,但 Scala 团队选择强迫你更清楚地表达你的意图。

怎么做?

明确意图 1:在 trait 中覆盖

trait T { def play(): Unit }
trait TA extends T { override def play() = println("TA") }
trait TB extends T { override def play() = println("TB") }
class MyClass extends TA with TB {}

您的第二个示例显示了一种表达覆盖意图的方式。由于TA.playTB.play 是用override 声明的,很明显它们打算覆盖范围内的方法play(): Unit。这就是为什么编译器允许TB.playMyClass 中“替换”TA.play

明确意图 2:在类中覆盖

class MyClass extends TA with TB {
  override def play(): Unit = {
    super.play() // super[TB].play()
    super[TA].play()
  }
}

重写类中的方法是解决问题的另一种方法。在这里,我们确切地知道MyClass.play 是什么,因为它是明确定义的。从MyClass的角度来看,没有歧义。

编译器可以使用super[TA].play(),因为它很清楚调用了哪个方法:def play() = println("TA")trait TA 中定义的那个。所以super[TA].play() 会执行println("TA")

super.play() 不太明确,但类组合的规则很明确:super 在此上下文中指的是最后一个 mixin,即它与 super[TB] 相同。

【讨论】:

  • 谢谢。但这仍然没有解释MyClass的超类中TB中的play的语义是什么。
  • the superclass of MyClass 是什么意思? MyClass 有两个 mixin,TA 和 TB,以及 jvm 上的“默认”超类 java.lang.Object
  • 我讲的是类线性化。 MyClass extends TA with TB => {MyClass, TB, TA}
  • 我编辑了最后一段以阐明 super[TA].play() 的作用。 TA 中的 play 方法不是未定义的(TB 中的 play 相同)。它被MyClass 中的play 覆盖,但仍然独立存在。
  • @ElectronWill 您在明确意图 1 中的代码无法编译,因为您忘记从特征 T 扩展 TA 和 TB - 没有什么可以覆盖的。你应该编辑你的答案:)
【解决方案2】:

您已经在子类中手动定义了行为。 你为什么惊讶?在这种情况下有什么不正确的?

我认为该示例中主要的混淆之处在于“super.play()”是合法的,并且与最后一个继承的成员 - TC 相关。我认为这只是我们采取最后一个的一种解决方法。 我想他们可以通过这种解决方法解决第一种情况(不编译) - 只需采用最后继承的实现。

【讨论】:

  • 令我惊讶的是为什么我不需要在TBTC 中将override 标记为play ?类MyClassTBTC 中的方法play 的语义是什么?它们是different methodsoverridehideundefinedconflicing?
  • 您说“我想他们可以通过这种解决方法解决第一种情况(无法编译)”。我不这么认为,因为正如我在问题中所说,Scala 中没有冲突成员的默认行为。如果 Scala 具有像 Java 一样的默认 override 行为,那么您是对的。
  • 好吧,TBTC 不知道 TAMyClass,因此在这里使用 override 是没有意义的。从他们的角度来看,他们的play 方法是唯一的。
  • @ElectronWill 如果“从他们的角度来看,他们的播放方法是唯一的。”,为什么第一个案例(在我的问题中)编译失败?
  • 第一种情况失败,因为从MyClass的角度来看有问题(但不是从traits的pov)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-29
  • 2015-10-14
  • 1970-01-01
相关资源
最近更新 更多