一、前言
前面學習了Scala中包和匯入的相關知識點,接著學習Traits(特質)
二、Traits
Scala的特質與Java的介面基本相同,當遇到可以使用Java介面的情形,就可以考慮使用特質,Scala的類可以使用extends和with關鍵字繼承多個特質,如類或物件繼承多個特質
class Woodpecker extends Bird with TreeScaling with Pecking
特質除了可以擁有Java中介面的抽象方法,同時還可以擁有已經實現的方法,可以將多餘一個的特質混入類,並且特質可以控制哪些類可以混入該特質
2.1 將特質作為介面
1. 問題描述
像其他語言如Java建立介面一樣,你想在Scala也建立類似東西
2. 解決方案
可以將特質類比為Java的介面,在特質中宣告需要子類實現的方法
trait BaseSoundPlayer {
def play
def close
def pause
def stop
def resume
}
如果方法不帶引數,則只需要寫方法名即可,但若帶引數,需要如下定義
trait Dog {
def speak(whatToSay: String)
def wagTail(enabled: Boolean)
}
當一個類繼承特質時,需要使用extends和with關鍵字,當繼承一個特質時,使用extends關鍵字
class Mp3SoundPlayer extends BaseSoundPlayer { ...}
繼承多個特質時,使用extends和with關鍵字
class Foo extends BaseClass with Trait1 with Trait2 { ...}
除非實現特質的類是抽象的,否則其需要實現特質中所有方法
class Mp3SoundPlayer extends BaseSoundPlayer { def play { // code here ... } def close { // code here ... } def pause { // code here ... } def stop { // code here ... } def resume { // code here ... } }
如果沒有實現所有抽象方法,則該類需要被申明為抽象的
abstract class SimpleSoundPlayer extends BaseSoundPlayer { def play { ... } def close { ... } }
特質也可以整合其他特質
trait Mp3BaseSoundFilePlayer extends BaseSoundFilePlayer { def getBasicPlayer: BasicPlayer def getBasicController: BasicController def setGain(volume: Double) }
3. 討論
特質可被作為Java的介面使用,並且在特質中申明需要被子類實現的方法,當繼承特質時使用extends或者with關鍵字,當繼承一個特質時,使用extends,若繼承多個特質,則第一個使用extends,其他的使用with;若類繼承類(抽象類)和特質,則抽象類使用extends,特質使用with,特質中可以有已經實現了的方法,如WaggingTail
abstract class Animal { def speak } trait WaggingTail { def startTail { println("tail started") } def stopTail { println("tail stopped") } } trait FourLeggedAnimal { def walk def run } class Dog extends Animal with WaggingTail with FourLeggedAnimal { // implementation code here ... def speak { println("Dog says 'woof'") } def walk { println("Dog is walking") } def run { println("Dog is running") } }
2.2 在特質中使用抽象和具體的欄位
1. 問題描述
你想要在特質中定義抽象和具體的欄位,以便繼承特質的類可以使用欄位
2. 解決方案
使用初始值賦值的欄位是具體的,沒有賦值的欄位是抽象的
trait PizzaTrait { var numToppings: Int // abstract var size = 14 // concrete val maxNumToppings = 10 // concrete }
繼承特質的類,需要初始化抽象欄位,或者讓該類為抽象類
class Pizza extends PizzaTrait { var numToppings = 0 // 'override' not needed size = 16 // 'var' and 'override' not needed }
3. 討論
特質中的欄位可以被宣告為val或var,你不需要使用override欄位來覆蓋var欄位,但是需要使用override來覆蓋val欄位
trait PizzaTrait { val maxNumToppings: Int } class Pizza extends PizzaTrait { override val maxNumToppings = 10 // 'override' is required }
2.3 像抽象類一樣使用特質
1. 問題描述
你想要像Java中的抽象類一樣使用特質
2. 解決方案
在特質中定義方法,繼承特質的類中,可以覆蓋該方法,或者是直接使用
trait Pet { def speak { println("Yo") } // concrete implementation def comeToMaster // abstract method } class Dog extends Pet { // don't need to implement 'speak' if you don't need to def comeToMaster { ("I'm coming!") } } class Cat extends Pet { // override the speak method override def speak { ("meow") } def comeToMaster { ("That's not gonna happen.") } }
如果一個類沒有實現特質的抽象方法,那麼它也需要被宣告為抽象的
abstract class FlyingPet extends Pet { def fly { ("I'm flying!") } }
3. 討論
一個類僅僅只能繼承一個抽象類,但是可以繼承多個特質,使用特質更為靈活
2.4 把特質作為簡單的混合物
1. 問題描述
你想要將多個特質混合進一個類中
2. 解決方案
為實現簡單的混合,在特質中定義方法,然後使用extends和with繼承特質,如定義Tail特質
trait Tail { def wagTail { println("tail is wagging") } def stopTail { println("tail is stopped") } }
可以使用該特質和抽象的Pet類來建立Dog類
abstract class Pet (var name: String) { def speak // abstract def ownerIsHome { println("excited") } def jumpForJoy { println("jumping for joy") } } class Dog (name: String) extends Pet (name) with Tail { def speak { println("woof") } override def ownerIsHome { wagTail speak } }
Dog類通知擁有特質Tail和抽象類Pet的行為
2.5 通過繼承控制哪個類可以使用特質
1. 問題描述
你想限制的特性,只能將其新增至一個父類或者另一個特質的類
2. 解決方案
使用下面語法宣告名為TraitName的特質,而TraitName只能被混入繼承了SuperThing的類,SuperThing可以是一個特質、抽象類、類。
trait [TraitName] extends [SuperThing]
例如,Starship和StarfleetWarpCore都繼承了StarfleetComponent,所以StarfleetWarpCore特質可以被混入Starship中
class StarfleetComponent trait StarfleetWarpCore extends StarfleetComponent class Starship extends StarfleetComponent with StarfleetWarpCore
然而,Warbird不能繼承StarfleetWarpCore特質,因為其不繼承StarfleetComponent類
class StarfleetComponent trait StarfleetWarpCore extends StarfleetComponent class RomulanStuff // won't compile class Warbird extends RomulanStuff with StarfleetWarpCore
3. 討論
一個特質繼承一個類不是一種普遍情況,但是當其發生時,需要保證擁有相同的父類
2.6 標記特質以使得其僅僅能被某種型別子類使用
1. 問題描述
你想要標記您的特性,因此只能用於擴充套件給定基型別的型別
2. 解決方案
保證MyTrait的特質僅僅只能被混入BaseType的子類,可以使用this: BaseType =>宣告開始特質
trait MyTrait { this: BaseType =>
例如,為了是StarfleetWarpCore特質只能用於Starship,可以標記StarfleetWarpCore特質如下
trait StarfleetWarpCore { this: Starship => // more code here ... }
給定上面的定義,下面程式碼將會執行良好
class Starship class Enterprise extends Starship with StarfleetWarpCore
而如下程式碼則編譯錯誤
class RomulanShip // this won't compile class Warbird extends RomulanShip with StarfleetWarpCore
3. 討論
任何混入特質的具體型別需要保證其能夠轉化為特質的自我型別,特質也可要求繼承其的子類必須繼承多種型別
trait WarpCore { this: Starship with WarpCoreEjector with FireExtinguisher => }
如下程式碼中的Enterprise將會通過編譯,因為其簽名滿足特質的定義
class Starship trait WarpCoreEjector trait FireExtinguisher // this works class Enterprise extends Starship with WarpCore with WarpCoreEjector with FireExtinguisher
若Enterprise不繼承特質,則無法滿足簽名,會報錯
2.7 保證特質只能被混入含有某特定方法的類
1. 問題描述
你想要將特質混入包含了某種方法簽名的類
2. 解決方案
WarpCore特質要求其所混入的類必須包含ejectWrapCore方法
trait WarpCore { this: { def ejectWarpCore(password: String): Boolean } => }
Enterprise類滿足要求,編譯成功
class Starship { // code here ... } class Enterprise extends Starship with WarpCore { def ejectWarpCore(password: String): Boolean = { if (password == "password") { println("ejecting core") true } else { false } } }
特質可以要求混入的類包含多個方法
trait WarpCore { this: { def ejectWarpCore(password: String): Boolean def startWarpCore: Unit } => } class Starship class Enterprise extends Starship with WarpCore { def ejectWarpCore(password: String): Boolean = { if (password == "password") { println("core ejected"); true } else false } def startWarpCore { println("core started") } }
3. 討論
該方法也被稱為結構型別,因為你規定了某些類必須具有的結構
2.8 將特質新增至物件例項
1. 問題描述
當物件例項建立時,你想要混入特質
2. 解決方案
可以使用如下方法
class DavidBanner trait Angry { println("You won't like me ...") } object Test extends App { val hulk = new DavidBanner with Angry }
當執行程式碼時,會出現You won't like me ...結果,因為Angry特質會被例項化
3. 討論
混入debugging和logging可能是更為常用的用法
trait Debugger { def log(message: String) { // do something with message } } // no debugger val child = new Child // debugger added as the object is created val problemChild = new ProblemChild with Debugger
2.9 像特質一樣繼承Java介面
1. 問題描述
你想要在Scala應用中實現Java介面
2. 解決方案
可以使用extends和with關鍵字繼承介面,如同繼承特質一樣,給定如下Java程式碼
// java public interface Animal { public void speak(); } public interface Wagging { public void wag(); } public interface Running { public void run(); }
你可以以Scala方式建立Dog類
// scala class Dog extends Animal with Wagging with Running { def speak { println("Woof") } def wag { println("Tail is wagging!") } def run { println("I'm running!") } }
區別在於Java介面不能實現方法,所以當繼承介面時,要麼實現所有方法,要麼宣告為抽象的
三、總結
本篇博文講解了Scala中的特質相關點,其可以類比於Java的介面,但是比介面更為靈活,如可新增欄位和已經實現方法(在Java 8後的介面也可以新增已實現的方法),謝謝各位園友的觀看~