Scala

  大数据Scala系列之特质, 特质的定义除了使用关键字trait之外,与类定义无异。

  特质用来在类之间进行接口或者属性的共享。类和对象都可以继承特质,特质不能被实例化,因此也没有参数。

   一旦特质被定义了,就可以使用extends或者with在类中混入特质。

作为接口使用的特质

特质的定义:

trait Logger{
     // 这是一个抽象方法,特质中未被实现的方法默认是抽象的,不需要 abstract 关键字修饰
      def log(msg:String)
}

子类对特质的实现:

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

带有具体实现的特质

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
    }
}

带有特质的对象

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

多个叠加的特质

可以为类或者对象添加多个互相调用的特质,特质的执行顺序,取决于特质被添加的顺序

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…

使用特质统一编程

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

当做富接口使用的特质

// 注意抽象方法和具体方法的结合
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
}

特质构造顺序

特质也是有构造器的,由字段的初始化和其他特质体中的语句构成:

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

初始化特质中的字段

特质不能有构造器参数,每个特质都有一个无参构造器。 这也是特质和类的差别。   例如:  我们要在构造的时候指定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!”
}

 

    原文作者:好程序员IT
    原文地址: http://blog.itpub.net/69913892/viewspace-2660823/
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞