Scala:abstract override
定义
官方文档对于abstract override
的定义是:
The
override
modifier has an additional significance when combined with theabstract
modifier. That modifier combination is only allowed for value members of traits.
We call a member M of a template incomplete if it is either abstract (i.e. defined by a declaration), or it is labeledabstract
andoverride
and every member overridden by M is again incomplete.
Note that theabstract override
modifier combination does not influence the concept whether a member is concrete or abstract. A member is abstract if only a declaration is given for it; it is concrete if a full definition is given.
这里面有三个要点:
-
修饰符
abstract override
只能用于特质(Trait)的成员 -
称类成员M是不完整的(incomplete),如果M是抽象的(如M只有声明没有定义),或者M被
abstract override
修饰,同时任何被M重载的成员也同样是不完整的。
数学定义:令- overrided(x) 为被x重载的成员
- 布尔函数 abstract(x)为x是抽象的(即只有定义)
- 布尔函数 abstractOverride(x)为x是被
abstract override
- 布尔函数 incomplete(x)为x是不完整的,
则
incomplete(M)=abstract(M)∨[ abstractOverride(M)∧incomplete( overrided(M))] -
abstract override
并不影响成员是具体的还是抽象的。如果成员只有声明则它就是抽象的,如果有具体定义,那它就是具体的。
注意到M被abstract override
修饰时,如果M重载的成员是具体的,那么M就不是 incomplete :
class Father{
def name="Father"
}
trait Son extends Father{
abstract override def name="Son"
}
val son=new Son{}
son.name //"Son"
上面代码中,特质Son
继承了类Father
,并且方法name
被重载。尽管name
被abstract override
修饰,但是因为name
重载的方法(即Father
的name
)并不是 incomplete ,所以特质Son
的方法name
也不是 incomplete。
这同时也说明了,尽管name
被abstract override
修饰,但是因为有具体定义,所以它是具体的而不是抽象的。
另外注意到语句val son=new Son{}
,Son
是特质不能被实例化,但是此处写法实际上类似于Java一样声明了一个匿名类,因为Son
中没有抽象成员,所以花括号为空。
多提一句,在官方文档中abstract和override定义分别是:
abstract
Theabstract
modifier is used in class definitions. It is redundant for traits, and mandatory for all other classes which have incomplete members. Abstract classes cannot be instantiated **with a constructor **invocation unless followed by mixins and/or a refinement which override all incomplete members of the class. Only abstract classes and traits can have abstract term members.
要点:
-
abstract
只能修饰类,成员不能使用abstract
- 特质没有必要声明
abstract
- 类中如果有不完整(incomplete)成员,则必须声明
abstract
- 抽象类不能实例化,除非被混入,且/或 被使用匿名类实现
- 只有抽象类或特质才能有抽象成员
override
The override modifier applies to class member definitions or declarations. It is mandatory for member definitions or declarations that override some other concrete member definition in a parent class. If an override modifier is given, there must be at least one overridden member definition or declaration (either concrete or abstract).
要点:
- 如果被重载的方法是父类中有具体定义的方法,则
override
是必须的,否则可省略(例如,实现特质的抽象方法,就没有必要写override
) - 如果用
override
修饰,则必然存在一个被重载成员的定义或声明,无论是抽象还是定义
说明
abstract override
应该兼具abstract
和override
两种特性。
首先,如果特质的超类中不存在M方法,则特质中定义M方法时不能使用abstract override
,因为会违反override
的定义。
其次考虑下面的例子:
abstract class Animal{
def bark //abstract method
}
trait Dog extends Animal{
abstract override def bark=println("WoooW!") //abstract override
}
new Dog{}.bark
error: object creation impossible,
since method bark in trait Dog of type => Unit
is marked `abstract' and `override',
but no concrete implementation could be found in a base class
new Dog{}.bark
^
发现,方法bark
尽管有具体定义,但是编译器无法创建匿名类对象,原因是“特质中的方法bark
被abstract override
修饰,但是无法在基类中找到具体的实现”。从这一点上讲,方法bark
此时也具有抽象性(abstract
)。
这是符合定义的,因为显然Dog.bark
被abstract override
修饰,且被重载方法Animal.bark
是 incomplete ,从而 Dog.bark
也是 incomplete的,自然会报错,即:
incomplete(
Dog.bark
)
=abstract(Dog.bark
)∨[ abstractOverride(Dog.bark
)∧incomplete( overrided(Dog.bark
))]
=False∨[ True∧incomplete(Animal.bark
)]
=False∨[ True∧True]
=True
为了解决这个问题,有两种:
只要让 被重载方法Animal.bark
不是 incomplete即可(incomplete( overrided(Dog.bark
))==False):
abstract class Animal{
def bark=println("Emmm....") //Now it's not incomplete
}
trait Dog extends Animal{
//So method bark is also not incomplete
abstract override def bark=println("WoooW!")
}
//the invocation makes sense
new Dog{}.bark //WoooW!
或者只要让重载方法Dog.bark
不被abstract override
修饰即可( abstractOverride(Dog.bark
)==False):
abstract class Animal{
def bark //abstract method
}
trait Dog extends Animal{
override def bark=println("WoooW!") //redundant modifier override
}
new Dog{}.bark //WoooW!
以上只是极端情况的演示,但是实际上没有必要这么做。
结论
只有在满足下面条件的情况下,我们才应在trait 中定义的某一方法之前添加
abstract 关键字:该方法调用了super 对象中的另一个方法,但是被调用的
这个方法在该trait 的父类中尚未定义具体的实现方法。
考虑下面的样板代码:
abstract class AbstractSuper{
def abstractMethod( Params ) : ReturnType
}
trait TraitSub extends AbstractSuper{
def abstractMethod( Params ) :ReturnType ={ //implements the abstract method
/**
重载代码实现
*/
}
}
如果重载代码实现逻辑中:
-
不含有父类的 incomplete 方法:即没有
super.method
,或逻辑中有super.method
但是super.method
不是 incomplete 方法,则直接实现即可,不必加override
修饰符,并且可以使用new TraitSub{}.abstractMethod
来引用。
注意- 这里的
super.method
指代父类中的任何 incomplete 方法,比如super.abstractMethod
或是其他什么的。 - 所谓“父类具有 incomplete 方法”,要么该方法就是抽象的(没有具体定义),要么其有定义,但是在定义内部包含了祖先类的 incomplete 方法,这是一个递归过程。
- 这里的
-
含有父类的 incomplete 方法:
TraitSub.abstractMethod
有具体定义,但是具体定义中又有类似super.abstractMethod
的incomplete方法,这样TraitSub.abstractMethod
方法本身就不具体,此时必须使用abstract override
来修饰,以此提醒编译器(和读者):尽管TraitSub.abstractMethod
提供了方法体,但方法并没有完全实现。
为什么abstract override
只能用来修饰特质的成员呢?假设可以用来修饰抽象类的方法,那么该方法本身就应该使用了父抽象类的抽象方法super.abstractMethod
,但是这在设计逻辑上是说不通的:父抽象类的抽象方法本就是交由子类来实现的,表示抽象的通用行为,子类却又在实现逻辑内部调用父抽象类的抽象方法,这是毫无道理的。在这里,super
就是指代父抽象类
那么特质为什么能反过来使用父类的抽象方法呢,这似乎也在逻辑上说不通?因为特质有一个很特殊的性质:特质中的super
是动态绑定的,你应该注意到上面的两类情况讨论中,我并没有使用AbstractSuper.abstractMethod
的写法而是写成super.abstractMethod
,也就是说,尽管TraitSub
继承了抽象类AbstractSuper
,但是它的super
并不指代父抽象类AbstractSuper
,而是在运行过程中动态绑定,所以在此之前都是不定的,是抽象的。
这样的性质使得特质变得可堆叠。考虑一个抽象类A
,其内有抽象方法m
,有一个具体类C
继承了A
并实现了方法m
,另外有一个特质T
也继承了A
,并且m
的实现内引用了super.m
,故被标明abstract override
。
现在假定有一个类P
,它继承了C
,设p
是P
的实例,那么使用p.m
实际就由C.m
代理。现在,将特质T
混入:class P extends C with T
,那么使用p.m
,根据特质的线性化(扁平化处理,类似python中的MRO),它将由T.m
进行代理,而在其内部的super.m
实际上绑定为C.m
,因此p.m
=>T.m ( C.m )
。
试想如果有很多特质,它们对方法m
具有多态性,那么按照不同顺序混入,super
也就绑定不同的实例,可能就展现为p.m
=>T1.m ( T2.m( ....Tn.m (C.m ))... )
,通过将特质堆叠,使得方法变得更有选择性和层次感。
特质的线性化顺序
线性化算法
(1) 当前实例的具体类型会被放到线性化后的首个元素位置处。
(2) 按照该实例父类型的顺序从右到左的放置节点,针对每个父类型执行线性化算法,并将执行结果合并。(我们暂且不对AnyRef 和Any 类型进行处理。)
(3) 按照从左到右的顺序,对类型节点进行检查,如果类型节点在该节点右边出现过,那么便将该类型移除。
(4) 在类型线性化层次结构末尾处添加AnyRef 和Any 类型。
如果是对值类(如Int、Short、Double等)执行线性化算法,请使用AnyVal 类型替代AnyRef 类型。
例如,有如下继承关系(伪代码):
class A
trait B (A)
trait C (A)
trait D (A)
trait E (C)
trait F (C)
class G (D,E,F,B)
对G进行线性化:
- 当前实例的具体类型会被放到线性化后的首个元素位置处。
【方法链】:G - 按照其父类型的顺序从右到左的放置节点
【方法链】:G B F E D - 针对每个父类型执行线性化算法
【方法链】:
G B F E D
G B A F C E C D A - 按照从左到右的顺序,对类型节点进行检查,如果类型节点在该节点右边出现过,那么便将该类型移除。
【方法链】:
G B (A) F (C) E C D A
G B F E C D A
因此对G的线性化即(从左至右为):GBFECDA。方法链(super
的绑定)也按照这个顺序进行。显然,根据特质不同的混入顺序,这个方法链也会不同。