3 回答

TA貢獻1873條經(jīng)驗 獲得超9個贊
我可以想到兩個差異
抽象類可以具有構(gòu)造函數(shù)參數(shù)以及類型參數(shù)。特性只能具有類型參數(shù)。有人討論過,即使將來特征也可以具有構(gòu)造函數(shù)參數(shù)
抽象類可與Java完全互操作。您可以從Java代碼中調(diào)用它們而無需任何包裝。特性只有在不包含任何實現(xiàn)代碼的情況下才可以完全互操作

TA貢獻2037條經(jīng)驗 獲得超6個贊
Scala編程中有一節(jié)稱為“是特質(zhì)還是不特質(zhì)?”。解決了這個問題。由于第一版可在線獲得,我希望可以在這里引用整個內(nèi)容。(任何認真的Scala程序員都應該買這本書):
每當實現(xiàn)行為的可重用集合時,您都必須決定要使用特征還是抽象類。沒有嚴格的規(guī)則,但是本節(jié)包含一些要考慮的準則。
如果該行為不會被重用,則將其設(shè)為一個具體的類。畢竟這不是可重用的行為。
如果可以在多個不相關(guān)的類中重用它,請使其成為特征。只有特征可以混入類層次結(jié)構(gòu)的不同部分。
如果要在Java代碼中從中繼承,請使用抽象類。由于具有代碼的特征沒有緊密的Java類似物,因此從Java類中的特征繼承往往很尷尬。同時,從Scala類繼承也與從Java類繼承一樣。唯一的例外是,僅具有抽象成員的Scala特性直接轉(zhuǎn)換為Java接口,因此即使您期望Java代碼從其繼承,您也可以隨意定義這些特性。有關(guān)如何一起使用Java和Scala的更多信息,請參見第29章。
如果打算以編譯形式分發(fā)它,并且希望外部團體編寫從其繼承的類,則可能傾向于使用抽象類。問題在于,當一個特性獲得或失去一個成員時,任何從其繼承的類都必須重新編譯,即使它們沒有更改。如果外部客戶只會調(diào)用行為,而不是繼承行為,那么使用特征就可以了。
如果效率很重要,則傾向于使用課程。大多數(shù)Java運行時使對類成員的虛擬方法調(diào)用比接口方法調(diào)用更快。特性被編譯到接口,因此可能會付出一些性能開銷。但是,僅當您知道所討論的特征構(gòu)成性能瓶頸并有證據(jù)證明使用類實際上可以解決問題時,才應做出此選擇。
如果仍然不知道,請在考慮了上述內(nèi)容之后,將其作為特征開始。您以后總是可以更改它,通常使用特征可以使更多選項保持打開狀態(tài)。
正如@Mushtaq Ahmed提到的,特征不能將任何參數(shù)傳遞給類的主構(gòu)造函數(shù)。
另一個區(qū)別是治療super
。
類和特性之間的另一個區(qū)別是,在類中,
super
調(diào)用是靜態(tài)綁定的,而在特性中,它們是動態(tài)綁定的。如果您super.toString
在類中編寫代碼,那么您將確切知道將調(diào)用哪種方法實現(xiàn)。但是,當您在特征中編寫相同內(nèi)容時,定義特征時,用于超級調(diào)用的方法實現(xiàn)是不確定的。
有關(guān)更多詳細信息,請參見第12章的其余部分。
編輯1(2013):
與特質(zhì)相比,抽象類的行為方式存在細微差異。線性化規(guī)則之一是,它保留了類的繼承層次結(jié)構(gòu),這傾向于將抽象類推到鏈的后面,而特征可以很容易地混合進來。在某些情況下,實際上最好放在類線性化的后一個位置,因此可以使用抽象類。請參閱Scala中的約束類線性化(混合順序)。
編輯2(2018):
從Scala 2.12開始,特征的二進制兼容性行為已更改。在2.12之前,向特性添加或刪除成員都需要重新編譯所有繼承特性的類,即使這些類沒有更改。這是由于在JVM中對特征進行編碼的方式所致。
從Scala 2.12開始,特征可以編譯到Java接口,因此要求有所放松。如果特征執(zhí)行以下任何一項操作,則其子類仍需要重新編譯:
定義字段(
val
或var
,但是常量可以-final val
沒有結(jié)果類型)呼喚
super
主體中的初始化器語句
擴展課程
依靠線性化在正確的特征中找到實現(xiàn)
但是,如果特征不具備,您現(xiàn)在可以在不破壞二進制兼容性的情況下對其進行更新。
添加回答
舉報