### 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. 特質中的構造順序
> **個人理解:**和類一樣,特質也有構造器,同樣特質中也存在構造執行順序的問題。記住特質的構造順序對寫程序是很有幫助的。
-
特質的構造器執行順序:
1. 首先調用超類構造器;
1. 特質構造器在超類構造器之后、類構造器之前執行;
1. 特質由左到右構造;
1. 每個特質中,父特質先被構造;
1. 如果過個特質共有一個父特質,而父特質已被構造,則不會再次被構造;
1. 所有特質構造完畢,子類被構造。
### 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()) }
}
~~~
【待續】