特質(zhì)
Scala里相當(dāng)于Java接口的是Trait(特征)。實(shí)際上它比接口還功能強(qiáng)大。與接口不同的是,它還可以定義屬性和方法的實(shí)現(xiàn)。Scala中特征被用于服務(wù)于單一目的功能模塊的模塊化中。通過混合這種特征(模塊)群來實(shí)現(xiàn)各種應(yīng)用程序的功能要求,Scala也是按照這個(gè)構(gòu)想來設(shè)計(jì)的。
特質(zhì)的構(gòu)造順序
特質(zhì)也可以有構(gòu)造器,由字段的初始化和其他特質(zhì)體中的語(yǔ)句構(gòu)成。這些語(yǔ)句在任何混入該特質(zhì)的對(duì)象在構(gòu)造時(shí)都會(huì)被執(zhí)行。
構(gòu)造器的執(zhí)行順序:
- 調(diào)用超類的構(gòu)造器;
- 特質(zhì)構(gòu)造器在超類構(gòu)造器之后、類構(gòu)造器之前執(zhí)行;
- 特質(zhì)由左到右被構(gòu)造;
- 每個(gè)特質(zhì)當(dāng)中,父特質(zhì)先被構(gòu)造;
- 如果多個(gè)特質(zhì)共有一個(gè)父特質(zhì),父特質(zhì)不會(huì)被重復(fù)構(gòu)造
- 所有特質(zhì)被構(gòu)造完畢,子類被構(gòu)造。
構(gòu)造器的順序是類的線性化的反向。線性化是描述某個(gè)類型的所有超類型的一種技術(shù)規(guī)格。
用作可堆疊改變的特質(zhì)
特質(zhì)的一個(gè)主要用法是把瘦接口(有較少的方法)轉(zhuǎn)變成胖接口(有較多的方法)。
其第二個(gè)主要用法是為類提供可堆疊的改變。
考慮一個(gè)整數(shù)隊(duì)列的例子。隊(duì)列有兩種操作:put,把整數(shù)放入隊(duì)列;get,從尾部取出整數(shù)。
import scala.collection.mutable.ArrayBuffer
abstract class IntQueue {
def get(): Int
def put(x: Int)
}
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) = { buf += x }
}
假設(shè)定義特質(zhì)執(zhí)行如下改動(dòng):
- Doubling: 把所有放入隊(duì)列的數(shù)字加倍
- Incrementing: 把所有放入隊(duì)列的數(shù)字增加
- Filtering:從隊(duì)列中過濾掉負(fù)整數(shù)
這三種特質(zhì)代表了改動(dòng),因?yàn)樗鼈兏淖兞嗽缄?duì)列類的行為而并非定義了全新的隊(duì)列類。這三種特質(zhì)是可堆疊的,你可以選擇它們混入類中,獲得所需改動(dòng)的全新的類。
//在特質(zhì)中重寫抽象方法
trait Doubling extends IntQueue {
abstract override def put(x: Int) { super.put(2*x) }
}
trait Incrementing extends IntQueue {
abstract override def put(x: Int) { super.put(x+1) }
}
trait Filtering extends IntQueue {
abstract override def put(x: Int){
if(x >= 0) super.put(x)
}
}
解釋:上面的代碼定義了超類IntQueue,這個(gè)定義意味著該特質(zhì)只能混入擴(kuò)展IntQueue的類中。
在特質(zhì)中聲明抽象方法中有super調(diào)用,在特質(zhì)里super調(diào)用時(shí)動(dòng)態(tài)綁定的;而在類中,由于繼承的抽象類,super調(diào)用時(shí)非法的。這里必須使用abstract override標(biāo)識(shí)符,它意味著特質(zhì)必須被混入某個(gè)具有期待方法的具體定義的類中,這種定義僅在特質(zhì)定義中使用。
演示:
scala> val queue = new BasicIntQueue with Doubling
queue: BasicIntQueue with Doubling = $anon$1@1d3eff4
scala> queue.put(10)
scala> queue.get()
res10: Int = 20
val queue = new BasicIntQueue with Doubling with Incrementing
queue: BasicIntQueue with Doubling with Incrementing = $anon$1@19de304
scala> queue.put(-1)
scala> queue.put(2)
scala> queue.put(3)
scala> queue.get
res3: Int = 0
scala> queue.get
res4: Int = 6
scala> queue.get
res5: Int = 8
混入的順序很重要,越靠近右側(cè)的特質(zhì)越先起作用。當(dāng)你調(diào)用帶混入的類的方法時(shí),最右側(cè)特質(zhì)的方法首先被調(diào)用。如果那個(gè)方法調(diào)用了super,它調(diào)用其左側(cè)特質(zhì)的方法,以此類推。上面的例子里,Incrementing的put首先被調(diào)用,然后Doubing的put第二個(gè)被調(diào)用。
特質(zhì)的線性化與多重集成
特質(zhì)是一種繼承多個(gè)類似于類的結(jié)構(gòu)的方式,但是它與多重繼承有很重要的區(qū)別。其中一個(gè)尤為重要:super的解釋。
對(duì)于多重繼承來說,super調(diào)用導(dǎo)致的方法調(diào)用可以在調(diào)用發(fā)生的地方明確決定;對(duì)于特質(zhì)來說,方法調(diào)用是由類和混入到類的特質(zhì)的線性化(linearization)所決定的。這種差別使得上面的特質(zhì)的堆疊成為可能。
在多重繼承的語(yǔ)言中,調(diào)用相同的方法,編譯規(guī)則會(huì)決定哪個(gè)超類最終勝出,而調(diào)用該超類的指定方法。
而在Scala中,當(dāng)你使用new實(shí)例化一個(gè)類的時(shí)候,Scala把這個(gè)類和所有它繼承的類還有他的特質(zhì)以線性的次序放在一起。然后,當(dāng)你在其中的一個(gè)類中調(diào)用super,被調(diào)用的方法就是方法鏈的下一節(jié)。除了最后一個(gè)調(diào)用super之外的方法,其凈結(jié)果就是可堆疊的行為。
線性化細(xì)節(jié)
Scala 的線性化的主要屬性可以用下面的例子演示:假設(shè)你有一個(gè)類 Cat,繼承自超類 Animal 以及兩個(gè)特質(zhì) Furry 和 FourLegged。 FourLegged 又?jǐn)U展了另一個(gè)特質(zhì) HasLegs:
class Animal
trait Furry extends Animal
trait HasLegs extends Animal
trait FourLegged extends HasLegs
class Cat extends Animal with Furry with FourLegged
類 Cat 的繼承層級(jí)和線性化次序展示在下圖。繼承次序使用傳統(tǒng)的 UML 標(biāo)注指明:白色箭頭表明繼承,箭頭指向超類型。黑色箭頭說明線性化次序,箭頭指向 super 調(diào)用解決的方向。
當(dāng)這些類和特質(zhì)中的任何一個(gè)通過super調(diào)用了方法,那么被調(diào)用的實(shí)現(xiàn)將是它線性化的右側(cè)的第一個(gè)實(shí)現(xiàn)。
參考資料
轉(zhuǎn)載請(qǐng)注明作者Jason Ding及其出處
GitCafe博客主頁(yè)(http://jasonding1354.gitcafe.io/)
Github博客主頁(yè)(http://jasonding1354.github.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡(jiǎn)書主頁(yè)(http://www.itdecent.cn/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354進(jìn)入我的博客主頁(yè)