快学SCALA(10)--特质

当做接口使用的特质:

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

class ConsoleLogger extends Logger with Cloneable with Serializable{
 def log(msg: String): Unit = {println(msg)}
}

  注:1. 在重写特质的抽象方法时不需要给出override关键字;

    2. 如果需要的特质不止一个,可以使用with关键字来添加额外的特质

带有具体实现的特质:

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

trait ConsoleLogger{
  def log(msg: String): Unit = {println(msg)}
}

class SavingAccounts extends Logger with ConsoleLogger{
  var balance = 0: Double

  def withdraw(amount: Double): Unit = {
    if(amount > balance) log("Insufficient funds")
    else balance -= amount
  }
}

  在这个例子中,SavingsAccount从ConsoleLogger特质得到了一个具体的log方法实现。用JAVA接口的话,这是不可能的。我们说ConsoleLogger的功能被“混入”了SavingsAccount类。

  注:但是让特质拥有具体行为存在一个弊端。当特质改变时,所有混入了该特质的类都必须重新编译。

带有特质的对象

可以在构建单个对象时添加特质,表示在构造对象的时候“混入”了更好的类,在这个时候优先执行构建对象时添加的特质内的方法。

叠加在一起的特质

可以为类或对象添加多个相互调用的特质,从最后一个开始。

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

trait ConsoleLogger extends Logger{
  override def log(msg: String): Unit = {println(msg)}
}

class SavingAccounts extends Logger with ConsoleLogger{

  var balance = 0: Double

  def withdraw(amount: Double): Unit = {
    if(amount > balance) log("Insufficient funds")
    else balance -= amount
  }
}

trait TimestampLogger extends Logger{
  override def log(msg: String): Unit = {
    super.log(new java.util.Date() + " " + msg)
  }
}

trait ShortLogger extends Logger{
  val maxLength = 15

  override def log(msg: String): Unit = {
    super.log(if(msg.length <= maxLength)msg else msg.substring(0, maxLength-3) + "...")
  }
}

object TestTrait {

  def main(args: Array[String]): Unit = {
    val acct1 = new SavingAccounts with ConsoleLogger with TimestampLogger with ShortLogger
    val acct2 = new SavingAccounts with ConsoleLogger with ShortLogger with TimestampLogger

    acct1.withdraw(1.0)
    acct2.withdraw(1.0)
  }

}

  执行TestTrait对象的main方法结果如下:

acct1首先执行ShortLogger的log方法,然后用super.log调用TimestampLogger

acct2正好相反

如果需要控制具体是哪一个特质的方法被调用,则可以在方括号中给出名称:super[ConsoleLogger].log(...)。这里给出的类型必须是直接超类型;你无法使用继承层级中更远的特质或类。

当做富接口使用的特质

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 SavingAccounts extends Logger{

  var balance = 0: Double

  def withdraw(amount: Double): Unit = {
    if(amount > balance) severe("Insufficient funds")
    else balance -= amount
  }

  override def log(msg: String): Unit = {println(msg)}
}

  

特质中的具体字段

特质中的字段可以是具体的,也可以是抽象的。如果给出了初始值,那么字段就是具体的。

来自特质的字段被加入子类字段。

特质中的抽象字段

特质中未被初始化的字段在具体的子类中必须被重写

特质构造顺序

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

  1. 首先调用超类的构造器
  2. 特质构造器在超类构造器之后、类构造器之前执行
  3. 特质由左到右被构造
  4. 每个特质当中,父特质优先被构造
  5. 如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被再次构造
  6. 所有特质构造完毕,子类被构造

举例来说,考虑如下一个类:

class SavingsAccount extends Account with FileLogger with ShortLogger

  构造器将按照如下的顺序构造

  1. Account(超类)
  2. Logger(第一个特质的父特质)
  3. FileLogger(第一个特质)
  4. ShortLogger(第二个特质,注意此时它的父特质Logger已经被构造)
  5. SavingsAccount(类)

初始化特质中的字段

特质不能有构造器参数,这是特质与类的唯一技术差别

在特质中放置一个抽象字段,在子类的构造函数中对这个抽象字段进行初始化是不可行的:

trait FileLogger extends Logger{

  val filename: String
  val out = new PrintStream(filename)

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

object TestTrait {

  def main(args: Array[String]): Unit = {
    val acct = new SavingAccounts with FileLogger {
      override val filename: String = "myapp.log"
    }
  }

}

  在这种情况下,由于FileLogger优先于子类被构造(子类就是一个扩展自SavingAccounts,混入FileLogger的匿名类),故在对FileLogger的out字段初始化的时候会抛出空指针异常。

有两种解决方法:

1. 提前定义:能够解决问题,但不是很漂亮

class SavingsAccount extends {  val filename = "savings.log"} with Account with FileLogger {...}  

  在FileLogger被构造的时候,filename已经是初始化过的了

2. 懒值

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

  如此一来,out字段将在初次被使用时才会初始化。而在那个时候,filename字段应该已经被设好值了。不过,由于懒值在每次使用前都会检查是否已经初始化,它们用起来并不是那么高效。

时间: 2024-08-24 20:47:36

快学SCALA(10)--特质的相关文章

快学Scala 第十九课 (trait的abstract override使用)

trait的abstract override使用: 当我看到abstract override介绍的时候也是一脸懵逼,因为快学scala,只介绍了因为TimestampLogger中调用的super.log依旧是个abstract class,所以必须在方法前加上abstract和override.但是并没有具体介绍如何使用,然后查阅了其他文档,才明白使用方法. 下面的代码定义了超类LoggerEmpty,这个定义意味着该特质只能混入扩展LoggerEmpty的类中. 在特质中声明抽象方法中有

快学Scala课后习题答案

分享一个之前做快学Scala的课后习题(2-21章节,19无)的Github链接,我把习题的文字写在了每个回答的注释上面,这样方便大家对照着看,省的回过头去对照着pdf看了,如果有做的不对的地方希望大家给予指正. 链接如下,http://github.com/fxxkinglife/scala-hello. 举一个第二章节的例子, object charpter02 { /* * 2.1 * 一个数字如果为正数,则它的signum为1; * 如果是负数,则signum为-1; * 如果为0,则s

快学scala 第十一章 操作符 读书笔记及习题答案代码

chapter 11 操作符 标签:快学scala 一.笔记 scala种可以在反引号中包含几乎任何字符序列, val 'val' = 42 所有的操作符都是左结合的,除了以冒号(:)结尾的操作符,和赋值操作符.用于构造列表的::操作符是又结合的.1::2::Ni1的意思是1::(2::Ni1),先创建出包含2的列表,这个列表又被作为尾巴拼接到以1作为头部的列表中. 2. 函数调用语法:f(arg1, arg2,...)扩展到可以应用于函数之外的值,如果f不是函数或方法,那么这个表达式等于f.a

快学Scala习题解答—第一章 基础

1 简介 近期对Scala比较感兴趣,买了本<快学Scala>,感觉不错.比<Programming Scala:Tackle Multi-Core Complexity on the Java Virtual Machine>好很多. 是本不错的入门书.而且每个章节都设置了难度级别,每章有习题,可以巩固Scala语法. 本文的目的就是针对这些习题进行解答 2 基础 2.1 在Scala REPL中键入3,然后按Tab键.有哪些方法可以被应用? 这个....直接操作一遍就有结果了.

快学scala笔记.

第一章 基础 val 定义的值实际上是一个常量 var 声明其值可变的变量 val xmax,ymax = 100 var greeting,message: String = null 1.3 常用类型 Scala的7种数值类型:Byte.Char.Short.Int.Long.Float和Double 1.toString() 2.to(10) "Hello".intersect("World") 1.4 算术和操作符重载 val answer = 8 * 5

快学Scala第13章----集合

本章要点 所有集合都扩展自Iterable特质 集合有三大类:序列.集.映射 对于几乎所有集合类,Scala都同时提供了可变的和不可变的版本 Scala列表要么是空的,要么拥有一头一尾,其中尾部本身又是一个列表 集是无先后次序的集合 用LinkedhashSet 来保留插入顺序,或者用SortedSet来按顺序进行迭代 '+' 将元素添加到无先后次序的集合中: +: 和 :+ 向前或向后追加到序列: ++将两个集合串接在一起: -和–移除元素 Iterable和Seq特质有数十个用于常见操作的方

[Scala] 快学Scala A2L2

集合 13.1 集合的三大类 所有的集合都扩展Iterable特质.集合的三大集合为Seq, Set, Map Seq是一个有先后次序的值的序列,比如数组或列表.IndexSeq允许我们通过整型下表快速访问任意元素.但是链表不可以. Set是一组没有先后次序的值.SortedSet中,元素排过序. Map时一组(键,值)对.SortedMap按键排序 每一个Scala集合特质或类都有一个带有apply 方法的伴生对象. 13.2 可变和不可变集合 13.3 序列Sequence 不可变序列:In

快学Scala第10章----特质

本章要点 类可以实现任意数量的特质 特质可以要求实现它们的类具备特定的字段.方法或超类 和Java接口不同,Scala特质可以提供方法和字段的实现 当你将多个特质叠加在一起时,顺序很重要--其方法先被执行的特质排在更后面 为什么没有多重继承 Scala和Java一样不允许类从多个超类继承:从多了超类继承可能会导致许多问题,例如两个超类有相同的方法,子类该如何使用和菱形继承.在java 中类只能扩展自一个超类,它可以实现任意数量的接口,但接口只能包含抽象方法,不能包含字段. Scala提供了特质(

快学Scala 2

控制结构和函数 1.在Scala中,几乎所有构造出来的语法结构都有值.这个特性是为了使得程序更加精简,也更易读. (1)if表达式有值 (2)块也有值——是它最后一个表达式的值 (3)Scala的for循环就像是“增强版”的Java for循环 (4)分号(在绝大多数情况下)不是必须的 (5)void类型是Unit (6)避免在函数定义中使用return (7)注意别在函数式定义中漏掉了= (8)异常的工作方式和Java中基本一样,不同的是catch语句中使用“模式匹配” (9)Scala没有受