1. 爲什麼沒有多重繼承
準備這個Chapter講文件和正則表達式,但該內容按照其他語言安排一般都是在最後幾章節,所以暫時先忽略該章節,後面會補上。
轉入正題,如果有兩個類Student和Employee,它們都有name這個屬性,如果要同時擴展這兩個基類該如何做,主要問題是name屬性如何處理?如果保留兩個,那麼訪問時到底訪問的是哪個呢?如果只保留一個,那麼保留哪一個?該問題就是菱形問題。畫一個形象的圖形如下:
Scala通過提供特質解決這個問題,特質可以同時擁有抽象方法和具體方法,而類可以實現多個特質。
2. 當做接口使用的特質
特質完全可以像Java接口那樣工作。
特質中未被實現的方法默認是抽象方法,不需要用
abstract
申明。重寫特質抽象方法時不需要給出
override
關鍵字。特質不止一個,可以使用
with
關鍵字來添加額外的特質。Scala類只能有一個超類,可以有任意數量的特質。
3. 帶有具體實現的特質
- 特質中可以有具體的方法。
trait ConsoleLogger {
def log(msg: String) { println(msg) } // 具體方法
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with ConsoleLogger {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
}
4. 帶有特質的對象
- 構造單個對象時,可以爲它添加特質,用
with
關鍵字。
trait Logged {
def log(msg: String) { }
}
trait ConsoleLogger extends Logged {
override def log(msg: String) { println(msg) }
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with Logged {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
}
object Main extends App {
val acct1 = new SavingsAccount
acct1.withdraw(100) // 沒有任何信息會打印出來
println("Overdrawing acct2");
val acct2 = new SavingsAccount with ConsoleLogger // 添加特質進來
acct2.withdraw(100) // 會打印信息
}
5. 添加多個特質
個人理解:添加多個特質要研究的問題實際是,添加的這幾個特質哪個先執行?即執行的順序,執行完的結果是要傳給另一個特質進行下一步處理,這裏有點像linux中pipe的概念。
- 類或對象可以添加多個特質,特質從最後一個開始執行,非常適合分階段處理的場景。
trait Logged {
def log(msg: String) { }
}
trait ConsoleLogger extends Logged {
override def log(msg: String) { println(msg) }
}
trait TimestampLogger extends Logged { // 給消息添加時間戳
override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger extends Logged { // 截斷過長的信息
val maxLength = 15
override def log(msg: String) {
super.log(
if (msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "...")
}
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with Logged {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds") // 調用特質中的log方法
else balance -= amount
}
}
object Main extends App {
val acct1 = new SavingsAccount with ConsoleLogger with
TimestampLogger with ShortLogger
val acct2 = new SavingsAccount with ConsoleLogger with
ShortLogger with TimestampLogger
acct1.withdraw(100)
acct2.withdraw(100)
}
- 由於特質添加的順序,上面的執行結果爲:
6. 在特質中重寫抽象方法
個人理解: 這節就是想區分用類來擴展特質是不需要加override,而特質擴展特質就要加override。
- 特質中重寫抽象方法需要加
abstract
和override
關鍵字。
trait Logger {
def log(msg: String) // 抽象的方法
}
trait TimestampLogger extends Logger {
abstract override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ConsoleLogger extends Logger {
override def log(msg: String) { println(msg) } // 這裏說明abstract是可以省略的
}
7. 當做富接口使用的特質
*個人理解:這節無非是上面的基本應用,train中可以根據不同的應用場景來定義多個同一功能的方法。
- 比如下面這段代碼,每個日誌消息都會區分消息的類型。
trait Logger {
def log(msg: String)
def info(msg: String) { log("INFO: " + msg) }
def warn(msg: String) { log("WARN: " + msg) }
def severe(msg: String) { log("SEVERE: " + msg) }
}
8. 特質中的具體字段
個人理解:這節主要目的是知道特質中可以有具體的字段,具體的意思就是經過初始化的字段。並且類在擴展特質後,具體字段不是被繼承的,而是直接被加入到子類中。說白了就是在類中又拷貝了該字段的一個副本,類中可以直接使用。
9. 特質中的抽象字段
個人理解:具體字段可以直接使用,那麼如果是抽象字段該怎麼辦?這節給出特質中如果有抽象字段,類中使用就必須要重新定義該字段,並且不需要使用override關鍵字,這在第二小節已經明確。
trait Logger {
val maxLength: Int // 抽象的字段
}
class TimestampLogger extends Logger {
val maxLength: Int = 20 // 不需要寫override
}
10. 特質中的構造順序
個人理解:和類一樣,特質也有構造器,同樣特質中也存在構造執行順序的問題。記住特質的構造順序對寫程序是很有幫助的。
特質的構造器執行順序:
- 首先調用超類構造器;
- 特質構造器在超類構造器之後、類構造器之前執行;
- 特質由左到右構造;
- 每個特質中,父特質先被構造;
- 如果過個特質共有一個父特質,而父特質已被構造,則不會再次被構造;
- 所有特質構造完畢,子類被構造。
11. 初始化特質中的字段
個人理解:首先知道特質是不能有構造參數,對某種定製的特質來說這就是一個問題,那麼該如何解決?這小結給給出了兩種解決方案:提前定義和懶值。
特質不能有構造參數,但都有一個無參的構造器,缺少構造器參數這是特質和類之間唯一的技術差別,特質可以具備類的所有特性。
提前定義的方法如下:
trait Logger {
def log(msg: String)
}
// 使用提前定義定義變量filename
trait FileLogger extends Logger {
val filename: String
val out = new PrintWriter(filename)
def log(msg: String) { out.println(msg); out.flush() }
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
}
object Main extends App {
val acct = new { // new之後就是提前定義塊,在構造器執行前就定義好
val filename = "myapp.log"
} with SavingsAccount with FileLogger
acct.withdraw(100)
}
- 懶值方法,懶值字段會在第一次使用時纔會被初始化。
trait Logger {
def log(msg: String)
}
// 使用懶值
trait FileLogger2 extends Logger {
val filename: String
lazy val out = new PrintWriter(filename)
def log(msg: String) { out.println(msg); out.flush() }
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
}
object Main extends App {
val acct2 = new SavingsAccount with FileLogger2 {
val filename = "myapp2.log"
}
acct2.withdraw(100)
}
12. 擴展類的特質
個人理解:標題有點繞,其實意思就是某個特質擴展了類,這種情況scala會有哪些特殊的地方需要注意。
- 特質是可以擴展特質的,其實特質也是可以擴展類,擴展後這個類自動成爲該特質的超類。
trait Logged {
def log(msg: String) { }
}
trait LoggedException extends Exception with Logged {
def log() { log(getMessage()) }
}
這裏LoggedException擴展了Exception類,並調用了從Exception超類繼承下來的getMessage()方法。
13. 自身類型
個人理解:除上面可以擴展某個類的方法之外,scala還提供另外一種方式,不用擴展某個類,直接把某個類作爲自身的一個類型,從而達到同樣的目的。
- 定義語法:
this: 類型 =>
,特質定義自身類型後,只能被混入指定類型的子類。比如:
trait Logged {
def log(msg: String) { }
}
trait LoggedException extends Logged {
this: Exception => // 特質不是擴展Exception類,而是有一個自身類型Exception
def log() { log(getMessage()) }
}
- 自身類型還可以用來處理結構類型,比如只指定一個方法,而不是類名。
trait Logged {
def log(msg: String) { }
}
trait LoggedException extends Logged {
this: { def getMessage() : String } =>
def log() { log(getMessage()) }
}
【待續】