Scala:abstract override

定義

官方文檔對于abstract override的定義是:

The override modifier has an additional significance when combined with the abstract 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 labeled abstractand override and every member overridden by M is again incomplete.
Note that the abstract 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被重載。盡管nameabstract override修飾,但是因為name重載的方法(即Fathername)并不是 incomplete ,所以特質Son的方法name也不是 incomplete
這同時也說明了,盡管nameabstract override修飾,但是因為有具體定義,所以它是具體的而不是抽象的。

另外注意到語句val son=new Son{}Son是特質不能被實例化,但是此處寫法實際上類似于Java一樣聲明了一個匿名類,因為Son中沒有抽象成員,所以花括號為空。

多提一句,在官方文檔中abstractoverride定義分別是:

abstract
The abstract 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應該兼具abstractoverride兩種特性。
首先,如果特質的超類中不存在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盡管有具體定義,但是編譯器無法創建匿名類對象,原因是“特質中的方法barkabstract override修飾,但是無法在基類中找到具體的實現”。從這一點上講,方法bark此時也具有抽象性(abstract)。

這是符合定義的,因為顯然Dog.barkabstract override修飾,且被重載方法Animal.barkincomplete ,從而 Dog.bark也是 incomplete的,自然會報錯,即:

??incomplete(Dog.bark)
=abstract(Dog.bark)∨[ abstractOverride(Dog.bark)∧incomplete( overrided(Dog.bark))]
=False∨[ Trueincomplete( Animal.bark)]
=False∨[ TrueTrue]
=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.abstractMethodincomplete方法,這樣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,設pP的實例,那么使用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進行線性化:

  1. 當前實例的具體類型會被放到線性化后的首個元素位置處。
    【方法鏈】:G
  2. 按照其父類型的順序從右到左的放置節點
    【方法鏈】:G B F E D
  3. 針對每個父類型執行線性化算法
    【方法鏈】:
    G B F E D
    G B A F C E C D A
  4. 按照從左到右的順序,對類型節點進行檢查,如果類型節點在該節點右邊出現過,那么便將該類型移除。
    【方法鏈】:
    G B (A) F (C) E C D A
    G B ????? F ????? E C D A

因此對G的線性化即(從左至右為):GBFECDA。方法鏈(super的綁定)也按照這個順序進行。顯然,根據特質不同的混入順序,這個方法鏈也會不同。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,731評論 18 399
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,810評論 18 139
  • 這兩年總看到有孩子不堪壓力而自殺的新聞,大部分是來自學習上的壓力,年紀輕輕就結束了自己的生命,令人唏噓。生命是那樣...
    紅塵紫陌閱讀 1,162評論 8 13
  • 趙薇曾在網上po了一張最新的證件照,并附言“很友善,符合我的審美! ”,看來趙導對這次的照片很滿意。那么問題來了,...
    樂像閱讀 342評論 0 0
  • 午覺起揭夢窗, 樹下重泉撲面, 碧巖鳥前紛紛。 不忍卒行,遂觀仁繼畫禪。 三杯以后,神態緩削, 水流沙河,隱隱秋聲...
    摩羯星一號閱讀 447評論 3 2