6

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

class A {
  def b { }
}

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

abstract class AA extends A {
  override def b
}

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

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

3 回答 3

6

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

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

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

于 2010-07-04T11:52:49.337 回答
2

问题非常微妙。根据经验,扩展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. 使b抽象(但你不想要那个)

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

于 2010-07-05T12:51:56.527 回答
2

不确定这是否是正确的解决方案,但如果您的trait Bextends A(和 override 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 强大的真正关键在于编译器在继承类中处理它们的方式。
Traits 实际上是 mixins,而不是真正的父类
任何非抽象特征成员实际上都包含在继承类中,就像类的物理部分一样。好吧,不是身体上的,但你明白了。
就好像编译器对非抽象成员执行剪切和粘贴并将它们插入到继承类中一样。这意味着继承路径没有歧义,这意味着没有钻石问题。

所以在你的第二个例子中不需要override关键字。

于 2010-07-04T11:28:59.823 回答