大數據Scala系列之特質

  大數據Scala系列之特質,特質的定義除了使用關鍵字trait之外,與類定義無異。

  特質用來在類之間進行接口或者屬性的共享。類和對象都可以繼承特質,特質不能被實例化,因此也沒有參數。

  一旦特質被定義了,就可以使用extends或者with在類中混入特質。

1 作爲接口使用的特質
特質的定義:

trait Logger{
//這是一個抽象方法,特質中未被實現的方法默認是抽象的,不需要abstract關鍵字修飾
def log(msg:String)
}

子類對特質的實現:

class ConsoleLogger extends Logger{
//重寫抽象方法,不需要override
def log(msg:String){println(msg)}
}

2 帶有具體實現的特質
trait ConsoleLogger{
//注意與Java中接口的不同
def log(msg:String){println(msg)}
}

特質的使用

class SavingAccount extends Account with ConsoleLogger{
def withdraw(amount:Double){
if(amount >balance) log("Insufficent funds")
else balance -= amount
}
}

3 帶有特質的對象
scala自帶有Logged特質,但是沒有實現

trait Logged{
def log(msg:String){}
}

如果在類定義中使用了該特質

//該類中,其中的日誌信息不會被記錄
class SavingAccount extends Account with Logged{
def withdraw(amount:Double){
if(amount >balance) log("Insufficent funds")
else balance -= amount
}
}

標準的ConsoleLogger擴展自Logger

class ConsoleLogger extends Logger{
//重寫抽象方法,不需要override
def log(msg:String){println(msg)}
}

可以在創建對象的時候,加入該特質:

val acct1=new SavingAccount with ConsoleLogger

這樣,創建同一類對象,卻可以加入不同的特質

val acct2=new SavingAccount with FileLogger

4 多個疊加的特質
可以爲類或者對象添加多個互相調用的特質,特質的執行順序,取決於特質被添加的順序

trait Logged{
def log(msg:String)
}
trait ConsoleLogger extends Logged{
//重寫抽象方法,不需要override
def log(msg: String) ={println(msg)}
}
//給log加上時間戳
trait TimestampLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(s"${java.time.Instant.now()} $msg")
}
}
//截斷過於冗長的日誌信息
trait ShortLogger extends ConsoleLogger{
val maxLength = 15
override def log(msg: String) {
super.log(
if(msg.length <=maxLength)msg
else
s"${msg.substring(0,maxLength-3)}...")
}
}
//定義超類
class Account {
protected var balance:Double = 0
}
class SavingAccount extends Account with ConsoleLogger{
def withdraw(amount:Double){
if(amount >balance) log("Insufficent funds")
else balance = balance - amount
}
}

object test{
def main(args: Array[String]): Unit = {
val acct1 = new SavingAccount with ConsoleLogger with TimestampLogger with ShortLogger
val acct2 = new SavingAccount with ConsoleLogger with ShortLogger with TimestampLogger
acct1.withdraw(100.0)
acct2.withdraw(100.0)

}
}

//res:
//ShortLogger的log方法先被執行,然後它的super.log調用的是TimestampLogger 的log方法,最後調用ConsoleLogger 的方法將信息打印出來
2018-06-15T16:50:28.448Z Insufficent ...
//先是TimestampLogger 的log方法被執行,然後它的super.log調用的是ShortLogger的log方法,最後調用ConsoleLogger 的方法將信息打印出來
2018-06-15T1...

5 使用特質統一編程
import scala.collection.mutable.ArrayBuffer

trait Pet {
val name: String
}

class Cat(val name: String) extends Pet
class Dog(val name: String) extends Pet

val dog = new Dog("Harry")
val cat = new Cat("Sally")

val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name)) // Prints Harry Sally

Mixins用於進行類組合的特質:

abstract class A {
val message: String
}
class B extends A {
val message = "I'm an instance of class B"
}
//此處的特質C即爲mixin
trait C extends A {
def loudMessage = message.toUpperCase()
}
class D extends B with C

val d = new D
println(d.message) // I'm an instance of class B
println(d.loudMessage) // I'M AN INSTANCE OF CLASS B

6 當做富接口使用的特質
//注意抽象方法和具體方法的結合
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)}
}
class Account {
protected var balance:Double = 0
}
class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) severe("Insufficient funds") else "you can do this" }
override def log(msg: String) { println(msg) }
}

object test{
def main(args: Array[String]): Unit = {
val acc = new SavingsAccount
acc.withdraw(100)
}
}
//result
SEVERE: Insufficient funds

7特質中的具體字段和抽象字段
特質中的字段有初始值則就是具體的,否則是抽象的。

trait ShortLogger extends Logged {
val maxLength = 15 // 具體字段
}

那麼繼承該特質的子類是如何獲得這個字段的呢。Scala是直接將該字段放入到繼承該特製的子類中,而不是被繼承。例如:

class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
var interest = 0.0
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else ...
}
}

特質中的抽象字段在具體的子類中必須被重寫:

trait ShortLogger extends Logged {
val maxLength: Int//抽象字段
override def log(msg: String) {
super.log( if (msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "...")
}
}

class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
val maxLength = 20 // 不需要寫override
}

8 特質構造順序
特質也是有構造器的,由字段的初始化和其他特質體中的語句構成:

trait FileLogger extends Logger {
val out = new PrintWriter("app.log") // 構造器的一部分
out.println("# " + new Date().toString) // 也是構造器的一部分

def log(msg: String) { out.println(msg); out.flush() }
}

這些語句在任何混入了該特質的對象在構造時都會被執行。 構造器的順序:

首先調用超類的構造器
特質構造器在超類構造器之後、類構造器之前執行
特質由左到右被構造
每個特質中,父特質先被構造
如果多個特質共有一個父特質,那麼那個父特質已經被構造,則不會被再次構造
所有特質構造完畢後,子類被構造。 例如:
class SavingsAccount extends Account with FileLogger with ShortLogger

構造器執行順序:

1Account (超類)

2 Logger (第一個特質的父特質)

3 FileLogger

4 ShortLogger

5 SavingsAccount

9 初始化特質中的字段
特質不能有構造器參數,每個特質都有一個無參構造器。這也是特質和類的差別。 例如: 我們要在構造的時候指定log的輸出文件:

trait FileLogger extends Logger {
val filename: String // 構造器一部分
val out = new PrintWriter(filename) // 構造器的一部分
def log(msg: String) { out.println(msg); out.flush() }
}

val acct = new SavingsAccount extends Account with FileLogger("myapp.log") //error,特質沒有帶參數的構造器

// 你也許會想到和前面重寫maxLength一樣,在這裏重寫filename:
val acct = new SavingsAccount with FileLogger {
val filename = "myapp.log" // 這樣是行不通的
}

FileLogger的構造器先於子類構造器執行。這裏的子類其實是一個擴展自SavingsAccount 並混入了FileLogger特質的匿名類。而filename的初始化發生在這個匿名類中,而FileLogger的構造器會先執行,因此new PrintWriter(filename)語句會拋出一個異常。 解決方法是要麼使用提前定義或者使用懶值:

val acct = new {
val filename = "myapp.log"
} with SavingsAccount with FileLogger

// 對於類同樣:
class SavingsAccount extends {
val filename = "myapp.log"
} with Account with FileLogger {
... // SavingsAccount 的實現
}

// 或使用lazy
trait FileLogger extends Logger {
val filename: String // 構造器一部分
lazy val out = new PrintWriter(filename) // 構造器的一部分
def log(msg: String) { out.println(msg); out.flush() }
}

10 擴展類的特質
特質也可以擴展類,這個類將會自動成爲所有混入該特質的超類

trait LoggedException extends Exception with Logged {
def log() { log(getMessage()) }
}

log方法調用了從Exception超類繼承下來的getMessage 方法。那麼混入該特質的類:

class UnhappyException extends LoggedException {
override def getMessage() = "arggh!"
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章