使用特征,不一致的编译器行为实现抽象方法?

时间:2010-07-04 10:23:15

标签: scala traits

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

class A {
  def b { }
}

我在Scala中扩展此类并覆盖该方法以使其成为抽象:

abstract class AA extends A {
  override def b
}

现在我在一个特性中实现这个方法:

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

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

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时不需要覆盖标签:

abstract class AA extends A {
  def b
}

3 个答案:

答案 0 :(得分:5)

为了试着看看发生了什么,我试了一下:

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")
             ^

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

答案 1 :(得分: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 overriden方法

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

  

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

     

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

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

答案 2 :(得分:2)

问题非常微妙。根据经验,您的 AA 类(扩展 A )应该与也扩展 A 的特征混合使用。

你做了:

class A {
  def b { }
}

abstract class AA extends A {
  override def b
}

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

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

要解决此问题,您需要确保两个方法override使用相同的方法,在这种情况下,编译器将理解您正在讨论SAME方法,并优先考虑混合的最后一个特征。 / p>

现在,要覆盖 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 (但您不希望如此)
  3. 同样,问题非常微妙,但我希望我能让它更清晰一些。要获得更好的理解,请阅读Scala's Stackable Trait Pattern