在Scala中的trait中引入了混入的概念,即Mixin of trait。 java
可能翻譯不許確,有人也稱之爲混入類(mixins),混入是一種組合的抽象類,主要用於多繼承上下文中爲一個類添加多個服務,多重繼承將多個 mixin 組合成一個類。例如,若是你有一個類表示「馬」,你能夠實例化這個類來建立一個「馬」的實例,而後經過繼承像「車庫」和「花園」來擴展它,使用 Scala 的寫法就是: 算法
val myHouse = new House with Garage with Garden從 mixin 繼承並非一個特定的規範,這只是用來將各類功能添加到已有類的方法。在 OOP 中,有了mixin,你就有經過它來提高類的可讀性。
object Test { def main(args: Array[String]): unit = { class Iter extends StringIterator(args(0)) with RichIterator[char] val iter = new Iter iter foreach System.out.println } }如Iter類經過RichIterator和StringIterator這兩個父類混入構成,第一個父類仍然稱爲超類(superclass),第二個父類則稱爲混入類(mixin)。
又叫菱形問題(有時叫作「致命的死鑽石」deadly diamond of death),描述的是B和C繼承自A,D繼承自B和C,若是A有一個方法被B和C重載,而D不對其重載,那麼D應該實現誰的方法,B仍是C?
A
↗ ↖
B C
↖ ↗
D
在C++中是經過虛基類virtual實現,並按照深度優先,從左到右的順序遍歷調用,雖然解決了菱形問題,但因爲C++包含指針,繼承關係上容易形成結構混亂的狀況。
那麼,Scala是如何處理這個菱形問題的。在這以前,首先了解Scala中幾個概念。 編程
編程語言性能的瓶頸關鍵在編譯器,Scala也不例外,Scala編譯器是直接將Scala編譯成.class文件的。而生成的class文件則取決於你如何定義。當你定義一個只包含方法聲明而不包含方法體的trait類,他會編譯成一個Java接口。你能夠使用javap –c <class file name>查看。例如,trait Empty{def e:Int}會產生以下的類: app
public interface Empty{ public abstract int e(); }若是trait聲明瞭具體的方法或代碼,Scala會生成兩個類:一個接口類和一個包含代碼的新類。當一個類繼承這個trait時,trait中聲明的變量將被複制到這個類文件中,而定義在trait中的方法做爲這個繼承類的外觀模式的方法。這個類調用這個方法時,將調用新類中的對應方法。
Scala 的基於混入的類構成(mixin class composition)體系是線性混入構成(linearmixin compostion)和對稱的混入模塊(mixin modules),以及traits這三者的融合。 ssh
Scala是經過類的全序化(Class Linearization),或稱做類的線性化。線性化指出一個類的祖先類是一條線性路徑的,包括超類(superclass)和特性(traits)。它經過兩步來處理方法調用的問題:
① 使用右孩子優先的深度優先遍歷搜索(right-first,depth-first search)算法進行搜索。
② 遍歷獲得的結構層次中,保留最後一個元素,其他刪除。
線性混入,便是指使用右孩子優先的深度優先遍歷搜索算法,列出層次結構(Scala class hierarchy),所以Scala多重繼承的混入類中,若是包含有混入類(Mixins,或稱爲混入組合),則多重繼承中老是選擇最右邊的(right-mostly)的實現方法。分析以下代碼: 編程語言
package net.scala.chapter3.test import *** /** * @author Barudisshu */ @FixMethodOrder(MethodSorters.JVM) class TestMixin extends AssertionsForJUnit with LazyLogging { @Test def test() { val mixin = Mixin("jijiang") mixin.foo("jijiang: ") } } trait jijiang { def foo(msg: String) = println(msg) } trait mama extends jijiang { val str1 = "mama: " override def foo(msg: String) = println(str1.concat(msg)) } trait papa extends jijiang { val str2 = "papa: " override def foo(msg: String) = println(str2 + msg) } class Mixin private(msg: String) extends jijiang { def this() = this("mixin") } object Mixin { // 若是包含菱形問題,則只執行最右邊的 def apply(msg: String) = new Mixin(msg) with papa with mama }
這裏,實際輸出結果爲mama: jijiang: ,說明,trait papa並無執行,trait mama符合最右(最後)的深度優先遍歷結果。 ide
惰性求值能夠說是函數式語言中不可避免的事實,慶幸的是,Scala是一門強靜態的語言,在執行效率上比動態語言要高效些。 函數
在上述例子中,println方法不是jijiang、papa、mama任何一方的成員,所以,面對惰性求值問題時老是會執行,可是,若是改成下面這樣: post
trait jijiang { def foo(msg: String) = println(msg) } trait mama extends jijiang { val str1 = "mama: " override def foo(msg: String) = super.foo(str1.concat(msg)) } trait papa extends jijiang { val str2 = "papa: " override def foo(msg: String) = super.foo(str2 + msg) } class Mixin private(msg: String) extends jijiang { def this() = this("mixin") } object Mixin { // 若是包含菱形問題,則只執行最右邊的 def apply(msg: String) = new Mixin(msg) with papa with mama // 因爲Scala的惰性求值問題,包含多重繼承的父類中的成員變量名稱應該不同,不然形成編譯錯誤 }
輸入結果是什麼?答案是papa: mama: jijiang: 可能會奇怪,難道使用了super關鍵字就能夠避免菱形問題?不是,輸出結果順序仍然是按照深度優先遍歷的順序,但有所不一樣。由於,在Scala中,super關鍵字是動態調用的,這意味着super中的方法並非立刻執行,而是在真正被調用時執行,即惰性求值。因此上述代碼中,會按照jijiang<-papa<-mama的順序組裝字符串,即str2+msg<-str1.concat(msg),而後打印輸出pirntln(str1.concat(str2+msg)),所以,在Scala中就能夠達到屏蔽菱形問題的做用。 性能
由於在new Mixin(msg) with papa with mama中,mama的super是papa,因此,下面代碼是等價的:
trait jijiang { def foo(msg: String) = println(msg) } trait mama extends jijiang { val str1 = "mama: " override def foo(msg: String) = super.foo(str1.concat(msg)) } trait papa extends jijiang { val str2 = "papa: " override def foo(msg: String) = println(str2 + msg) } class Mixin private(msg: String) extends jijiang { def this() = this("mixin") }