首页 热点专区 小学知识 中学知识 出国留学 考研考公
您的当前位置:首页正文

好程序员大数据教程分享Scala系列之特质

2024-12-07 来源:要发发知识网

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

  一旦特质被定义了,就可以使用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.ArrayBuffertrait Pet {  val name: String}class Cat(val name: String) extends Petclass Dog(val name: String) extends Petval 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即为mixintrait C extends A {  def loudMessage = message.toUpperCase()}class D extends B with Cval d = new Dprintln(d.message)  // I'm an instance of class Bprintln(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)  }}//resultSEVERE: 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() }}

这些语句在任何混入了该特质的对象在构造时都会被执行。构造器的顺序:

[if !supportLists]• [endif]首先调用超类的构造器

[if !supportLists]• [endif]特质构造器在超类构造器之后、类构造器之前执行

[if !supportLists]• [endif]特质由左到右被构造

[if !supportLists]• [endif]每个特质中,父特质先被构造

[if !supportLists]• [endif]如果多个特质共有一个父特质,那么那个父特质已经被构造,则不会被再次构造

[if !supportLists]• [endif]所有特质构造完毕后,子类被构造。例如:

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的实现}//或使用lazytrait 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!"}

显示全文