Scala学习(十)特质

Scala提供“特质”而非接口。特质能够同时拥有抽象方法和具体方法,而类能够实现多个特质。java

1.看成接口使用的特质

Scala特质彻底能够像Java的接口那样工做。例如:app

trait Logger{
    def log(msg: String)  //这个是抽象方法
}

注意,你不须要讲方法声明为abstract,特质中未被实现的方法默认就是抽象的。ide

子类能够给出实现:工具

class ConsoleLogger extends Logger{  //用extends,而不是implements
    def log(msg: String){ println(msg) }  //不须要写override
}

若是你须要的特质不止一个,能够用with关键字来添加额外的特质:this

class ConsoleLogger extends Logger with Cloneable with Serializable

全部的Java接口均可以做为Scala特质使用,和Java同样,Scala类只能有一个超类,但能够有任意数量的特质。spa

2.带有具体实现的特质

在Scala中,特质中的方法并不须要必定是抽象的。举例来讲,咱们能够把咱们的具体类变成一个特质:scala

trait Logger{  
    def log(msg: String){ println(msg) }  
}

如何使用:日志

class ConsoleLogger extends Logger{
    def write(msg: String){ log("Hello World")}  
}

3.带有特质的对象

首先,特质也是能够被奇特特质扩展的code

trait Logger{  
    def log(msg: String){ log("Hello World") }  
}

trait ConsoleLogger extends Logger{
    override def log(msg: String){ println(“ConsoleLogger:” + msg) }  
}

trait FileLogger extends Logger{
    override def log(msg: String){ println(“FileLogger:” + msg) }  
}

你能够在构造对象的时候介入这个特质:server

val acct = new SavingsAccount with ConsoleLogger 

当咱们在acct对象上调用log方法时,ConsoleLogger特质的log方法就会被执行。固然了,另外一个对象能够加入不一样的特质:

val act = new new SavingsAccount with FileLogger 

4.叠加在一块儿的特质

//日志特质
trait Logger{
    def log(msg: String){} 
}

//打印日志
trait ConsoleLogger{ 
    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 SavingsAccount extends Logged{
    def withdraw(amount: Double){
        if(amount: Double) log("Insufficient funds")
        else 
        ...
    }
}

特质的调用,要根据特指添加的顺序界定。通常来讲特质是从最后一个开始被处理,例如:

val acct1 = new SavingsAccount with ConsoleLogger with TimestampLogger with ShortLogger

val acct2 = new SavingsAccount with ConsoleLogger with ShortLogger with TimestampLogger

若是咱们调用acct1中的withdraw方法,咱们将获得这样的消息:

Sun Feb 06 17:45:45 ICT 2011 Insufficient ...

由于ShortLogger首先执行,而后他的super.log调用的是TimestampLogger。
可是,执行acct2的withdraw方法输出的是:

Sun Feb 06 1...

这里,TimestampLogger在特质列表中最后出现。器log方法首先被调用,以后调用ShortLogger被截断

5.在特质中重写抽象方法

//日志特质
trait Logger{
    def log(msg: String) //这是个抽象方法
}

如今,让咱们用时间戳特质来扩展它,就像前一节中示例那样。很不幸,TimestampLogger 特质不能编译了。

//给日志添加时间戳
trait TimestampLogger extends Logged{
    override def log(msg: String){  //重写抽象方法
        super.log(new java.util.Date() + " " + msg) //super.log定义了吗?

    }
}

根据正常的继承规则,这个调用永远都是错的,Logger.log方法没有实现。但实际上,吉祥前一节看到的咱们无法知道那个log方法最终被调用,这个取决于特质被混入的顺序。

Scala认为TimestampLogger 依旧是抽象的,所以必须给方法加上abstract关键字以及override关键字:

trait TimestampLogger extends Logged{
    abstract override def log(msg: String){  
        super.log(new java.util.Date() + " " + msg) 

    }
}

6.当作富借口是用的特质

特质包含了大量的工具方法,而这些工具方法能够依赖一些抽象方法来实现。

trait Logger{
    def log(msg: String) //这是个抽象方法
    def info(msg: String) {log("INFO: " + msg)}
    def warn(msg: String) {log("WARN: " + msg)}
    def servere(msg: String) {log("SEVERE: " + msg)}
}

注意咱们是怎样把抽象方法和具体方法结合在一块儿的。

这样使用Logger特质的类就能够任意调用这些日志消息方法了。例如:

//存款类
class SavingsAccount extends Logged{
    def withdraw(amount: Double){
        if(amount: Double) servere("Insufficient funds")
        else 
        ...
    }

    ...

    override def log(msg: String){ println(msg); }

}

7.特质中的具体字段

特质中的字段能够试具体的能够试抽象额,若是给了初始值,name字段就是具体的。

一般,对于特质中每个具体字段,使用该特制的类会得到一个字段预支对应。这些字段不是被继承的;他们只是简单的被加入到了子类当中。

在JVM中,一个类智能扩展一个超类,所以来自特质的字段不能以相同的方式继承。因为这个限制,特质中的字段被接入到了子类当中,并不是父类字段的继承。

8.特质构造顺序

构造器以以下顺序执行:

  • 首先调用超累的构造器。
  • 特质构造器在超累构造以后、类构造器以前执行。
  • 特质由左到右被构造。
  • 每一个特质当中,父特质先被构造。
  • 若是多个特质共有一个父特质,而那个父特质已经被构造,则不会再次构造
  • 全部特质构造完毕,子类被构造。

例如:

class SavingAccount extends Account with FileLogger with ShortLogger

构造器将按照以下的顺序执行:

  1. Account(超类)
  2. Logger(第一个特质的父特质)
  3. FileLogger(第一个特质)
  4. ShortLogger(第二个特质)。注意他的父特质L噢GG而已被构造
  5. SavingsAccount(类)

9.初始化特质中的字段

特质不能有构造器参数。每一个特质都有一个无参数的构造器。

说明:缺乏构造器参数是特质与类之间惟一的技术差异

提早定义:

trait Logger {
    def log(msg: String)
}

trait FileLogger extends Logger{
    val filename: String
    val out = new PrintStream(filename)
    def log(msg: String){ out.println(msg); out.flush()}
}

class SavingsAccount extends Logged{
    def withdraw(amount: Double){
        if(amount: Double) servere("Insufficient funds")
        else 
        ...
    }

    ...

    override def log(msg: String){ println(msg); }

}

val acct = new SavingsAccount  with FileLogger {

val filename = "Hello" //将会报错,由于他的初始化顺序在FileLogger 以后,使得FileLogger初始化时filename 字段未完成初始化报错

}

解决办法:

val acct = new {
    val filename = "Hello"
} with SavingsAccount  with FileLogger 

若是你须要在类中作一样的事,能够这样

class SavingsAccount extends{
    val filename = "Hello"
}with FileLogger {

    ...

}

或者在FileLogger定义中使用懒值,可是并不高效

trait FileLogger extends Logger{
    val filename: String
    lazy val out = new PrintStream(filename)
    def log(msg: String){ out.println(msg); out.flush()}
}

10.扩展类的特质

一种不常见的用法是,特质也能够扩展类。这个被特质扩展的类将会自动成为全部混入该特质的超类。

假定有以下代码:

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

class UnhappyException extends LoggedException{

   ...

}

那么UnhappyException的超类自动变成LoggedException特质的扩展类Exception

若是咱们的类已经扩展了另外一个类怎么办?不要紧,只要是特质的超类的一个子类便可。若是咱们的类扩展了一个不相关的类,那么就不可能混入这个特质了。

11.自身类型

当特质扩展类时,编译器可以确保的一件事是全部混入该特质的类都认为这个类做超类。Scala还有另外一套机制能够保证这一点:自身类型。

当特质以以下代码开始定义时:

this:类型 =>

它便只能被混入指定类型的子类

trait FileLogger extends Logger{
    this: Test => //Test必须是接口或者特质
    def log(msg: String){ println(msg); }
}

自身类型一样也能够处理结构类型,这种类型只给出类必须拥有的方法,而不是特质或接口名称:

trait LoggedException extends Logger{
    this: {def getMessage(): String} => 
    def log(msg: String){ println(msg); }
}

这个特质能够被混入任何拥有getMessage方法返回值类型为String的类

相关文章
相关标签/搜索