【问题标题】:Implementing an abstract method with a trait, inconsistent compiler behaviour?实现具有特征、编译器行为不一致的抽象方法?
【发布时间】:2010-07-04 10:23:15
【问题描述】:

我有一个来自 Java 库的基类,我无法修改其代码。这个类 (A) 有一个空方法 (b),它应该被声明为抽象:

class A {
  def b { }
}

我在 Scala 中扩展了这个类并重写了方法以使其抽象:

abstract class AA extends A {
  override def b
}

现在我在一个 trait 中实现这个方法:

trait B {
  def b { println("B") }
}

如果我用特征 B 扩展 AA,我会得到一个错误:覆盖类型 A 中的方法 b => Unit; 类型 B 中的方法 b => 单元需要 `override' 修饰符:

class C extends AA with B {}

相反,如果代码是这样的,一切都编译没有错误,这对我来说似乎有点矛盾:

abstract class AA {
  def b
}

trait B {
  def b { println("B") }
}

class C extends AA with B {}

我正在运行 Scala 2.8.0RC3,并且对这种语言完全陌生(3 天)。另一个奇怪且相关的行为是在使 b 抽象时不需要覆盖标签:

abstract class AA extends A {
  def b
}

【问题讨论】:

  • 这是否与您想通过混合您自己在 trait 中定义的 def toString():String 实现来替换 Java 类的 Java toString 实现一样的问题?
  • 不,情况不同。在这种情况下,编译器的行为完全有意义,因为应该使用哪个定义是模棱两可的:Java toString 或 mixin 之一。不过,就我而言,只有一个可用。

标签: scala traits


【解决方案1】:

为了看看发生了什么,我尝试了这个:

scala> class A{
     |   def b{ }
     | }
defined class A

scala> abstract class AA extends A{
     |   override def b
     | }
defined class AA

scala> class AAA extends AA{
     |   def b = println("AAA")
     | }
<console>:8: error: overriding method b in class A of type => Unit;
 method b needs `override' modifier
         def b = println("AAA")
             ^

显然,问题的根源在于抽象类无法“释放”其超类中的方法,因为抽象类的子类需要包含“覆盖”修饰符。

【讨论】:

  • 好吧,通过在 C 类中使用 override def b = super[B].b 取消编译器的歧义,可以轻松修复覆盖问题,但这不是必需的。这就是我的观点。谢谢!
  • MJP 是对的:具体成员永远不能在子类中抽象化。子类中的抽象成员声明无效。这是 mixin 组合规则的结果,无论包含定义的特征的顺序如何,具体总是优先于抽象。
【解决方案2】:

不确定这是否是正确的解决方案,但如果您的 trait B 扩展 A(并覆盖 b),那么一切编译正常:

首先让我们定义AAA,就像您在问题中提出的那样:

C:\Users\VonC>scala
Welcome to Scala version 2.8.0.RC5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_18).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class A {
     | def b { println("A b") }
     | }
defined class A

scala> new A
res5: A = A@153bedc4

scala> res5.b
A b

scala> abstract class AA extends A {
     | override def b
     | }
defined class AA

你做了什么:

scala> trait B {
     | override def b { println("B b") }
     | }
<console>:6: error: method b overrides nothing
       override def b { println("B b") }
                    ^

我对特征 B 的尝试(为了能够添加“override”):

scala> trait B extends A {
     | override def b { println("B b") }
     | }
defined trait B

那么现在:

scala> class C extends AA with B {}
defined class C

scala> new C
res7: C = C@1497b7b1

scala> res7.b
B b

正确的b重写方法是用C.b调用的


至于您明显的“不一致”,请参阅Scala for Java Refugees Part 5: Traits and Types

首先,有一个令人讨厌的 override 关键字。我在关于基本 OOP 的文章中提到,任何覆盖超类中的方法的方法都必须使用 override 修饰符声明。当时,我把它比作强制使用 @Override 注释的语言,其主要目的是强制执行良好实践。

trait 真正强大的关键在于编译器在继承类中处理它们的方式。
trait 实际上是 mixins,而不是真正的父类
任何非抽象特征成员实际上都包含在继承类中,就像类的物理部分一样。好吧,不是身体上的,但你明白了。
就好像编译器对非抽象成员执行剪切和粘贴并将它们插入到继承类中一样。这意味着继承路径没有歧义,这意味着没有菱形问题。

因此,第二个示例中不需要 override 关键字。

【讨论】:

  • 感谢您的详细回复。我想我理解特性,因为我来自 Ruby,它有模块,有点像。恕我直言,让特征 B 扩展 A 违背了特征的目的,因为我不能在另一个类层次结构中使用这个特征,这正是我想要做的。有任何想法吗?我认为问题在于 Scala 编译器,它没有看到方法 b 已经变得抽象,并且在叶类 C 中混合特征时其定义没有歧义。
  • @A.R:我明白;我的回答受到blog.objectmentor.com/articles/2008/09/29/a-scala-style-with-construct-for-ruby 的启发。仍在检查其他解决方案。
【解决方案3】:

这个问题非常微妙。根据经验,扩展 A 的类 AA 应该与同样扩展 A 的特征混合。

你做到了:

class A {
  def b { }
}

abstract class AA extends A {
  override def b
}

trait B {
  def b { println("B") }
}

因此,当您混合使用 AAB 方法时,b 会被定义两次。一次由 A (未覆盖,因为 B 中的定义替换了 AA 中的覆盖),第二次由 B ,编译器不能选择一个,因为两个(同名但不相关)方法之间没有层次结构。如果你愿意,可以这样想:编译器“混合”了 AAB 的主体;如果他从 AA 中选择方法,它将是抽象的,如果他从 B 中选择方法(应该发生什么),因为它不是覆盖,所以你坚持使用两种方法b

为了解决这个问题,你要确保两个方法override 是同一个方法,在这种情况下编译器会理解你说的是同一个方法,并且会优先考虑最后混合的特征。

现在,要覆盖 B 中的方法 b,该类还必须继承自 A。因此,执行此操作的规范方法是:

class A {
  def b { }
}

abstract class AA extends A {
  override def b
}

trait B extends A{
  def b { println("B") }
}

class C extends AA with B {}

编译得很好。

现在,当你这样做时:

abstract class AA {
  def b
}

trait B {
  def b { println("B") }
}

class C extends AA with B {}

很明显这两种方法是相同的,所以编译器知道他必须使用特征中的方法。

其他解决方案包括:

  1. 使 B 覆盖 AA
  2. A 中制作 b 抽象(但你不希望这样)

同样,这个问题非常微妙,但我希望我把它说得更清楚一点。为了更好地理解,请阅读Scala's Stackable Trait Pattern

【讨论】:

    猜你喜欢
    • 2012-11-26
    • 1970-01-01
    • 2016-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多