Implementing an abstract method with a trait, inconsistent compiler behaviour?
I have a base class that comes from a Java library, whose code I cannot modify. This class (A) has an empty method (b) which should have been declared as abstract instead:
class A {
def b { }
}
I extend this class in Scala and override the method to make it abstract:
abstract class AA extends A {
override def b
}
Now I implement this method in a trait:
trait B {
def b { println("B") }
}
If I extend AA with trait B, I get a error: overriding method b in class A of type => Unit; method b in trait B of type => Unit needs `override' modifier:
class C extends AA with B {}
Instead, if the code had been like this, everything is compiled without errors, which seems a bit contradictory to me:
abstract class AA {
def b
}
trait B {
def b { println("B") }
}
class C extends AA with B {}
I'm running Scala 2.8.0RC3, and completely new to the language (3 da开发者_运维技巧ys). Another weird and related behaviour is that the override label is not necessary when making b abstract:
abstract class AA extends A {
def b
}
To try to see what's going on, I tried this:
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")
^
Apparently, the source of the problem is that abstract classes can't "free up" methods in their superclass from the need for the abstract class's subclasses to include the 'override' modifier.
Not sure if this is the right solution, but if your trait B
extends A
(and override b
), then everything compile fine:
First let's define A
and AA
like you present them in your question:
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
What you did:
scala> trait B {
| override def b { println("B b") }
| }
<console>:6: error: method b overrides nothing
override def b { println("B b") }
^
What I tried with trait B (in order to be able to add the 'override
'):
scala> trait B extends A {
| override def b { println("B b") }
| }
defined trait B
So now:
scala> class C extends AA with B {}
defined class C
scala> new C
res7: C = C@1497b7b1
scala> res7.b
B b
The right b
overriden method is called with C.b
As for your apparent "inconsistency", see Scala for Java Refugees Part 5: Traits and Types:
To start with, there’s that ever-annoying
override
keyword. I mentioned back in the article on basic OOP that any method which overrides a method in a superclass must be declared with the override modifier. At the time, I likened it to the language mandating the use of the@Override
annotation, with its primary purpose being to enforce the good practice.The real key to the power of traits is the way in which the compiler treats them in an inheriting class.
Traits are actually mixins, not true parent classes.
Any non-abstract trait members are actually included in the inheriting class, as in physically part of the class. Well, not physically, but you get the picture.
It’s as if the compiler performs a cut-and-paste with the non-abstract members and inserts them into the inheriting class. This means that there’s no ambiguity in the inheritance path, meaning no diamond problem.
So no need for override
keyword in your second example.
The problem is very subtle. As a rule of thumb, your class AA, which extends A, should be mixed with traits that also extend A.
You did:
class A {
def b { }
}
abstract class AA extends A {
override def b
}
trait B {
def b { println("B") }
}
Hence when you mix AA and B method b is DEFINED twice. Once by A (Not overrided because the definition in B replaced the override in AA) and the second by B, the compiler can't chose one over the other because there is no hierarchy between the two (equaly named but unrelated) methods. If you want, think about it like this: The compiler "mixes" the bodies of AA and B; if he choses the method from AA it will be abstract, if he choses the method from B (What should happend), since it's not an override, your stuck with two methods b.
To solve this, you want to make sure that both methods override
the same method, in which case the the compiler will understand you are talking about the SAME method and will give priority to the last trait mixed.
Now, to override the method b in B, that class has also to inherit from A. So the canonical way to do this would be:
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 {}
Which compiles just fine.
Now, when you do:
abstract class AA {
def b
}
trait B {
def b { println("B") }
}
class C extends AA with B {}
it's clear that both methods are the same so the compiler knows he has to use the method from the trait.
Other solutions include:
- Make B override AA
- Make b in A abstract (But you didn't want that)
Again, the problem is very subtle but I hope I made it a little clearer. To get a better understandig read Scala's Stackable Trait Pattern.
精彩评论