定義
官方文檔對于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
的綁定)也按照這個順序進行。顯然,根據特質不同的混入順序,這個方法鏈也會不同。