【scala 笔记(8)】 特质 -- trait

本文详细介绍了Scala中的特质(trait),包括当做接口使用、带有具体实现、带有特质的对象、特质叠加、重写抽象方法、作为富接口及特质中的字段和构造顺序。特质提供了比Java接口更强大的功能,如包含具体实现,可以在对象和类中灵活叠加,构造顺序也有所不同。
摘要由CSDN通过智能技术生成

特质(trait)是scala里代码复用的基础单元。 特质封装了方法和字段的定义, 并可以通过混入到类中复用它们。 与类的继承时每个类都只能继承唯一的超类不同, 类可以混入任意多个特质。

当做接口使用的特质

Scala的特质完全可以像Java的接口那样工作。例如:

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

class ConsoleLogger extends Logger{ // 使用 extends 而不是Java 中的 implements
  // 不需要写 override
  def log(msg: String): Unit = {
    println(msg)
  }
}

trait 不需要你将方法声明为abstract,未被实现的方法默认就是抽象的; 在重写特质的抽象方法时也不需要给出override关键字。

使用反编译看下上述样例Java的定义:

// borey-zhu@AWS-TEST-DT:~/scala$ scalac log.scala 
// borey-zhu@AWS-TEST-DT:~/scala$ ls
// ConsoleLogger.class  Logger.class  log.scala
// borey-zhu@AWS-TEST-DT:~/scala$ javap  -p  Logger.class 
// Compiled from "log.scala"
public interface Logger { // 生成java接口
  public abstract void log(java.lang.String);
}
// borey-zhu@AWS-TEST-DT:~/scala$ javap  -p  ConsoleLogger.class 
// Compiled from "log.scala"
public class ConsoleLogger implements Logger { // 实现接口
  public void log(java.lang.String);
  public ConsoleLogger();
}

带有具体实现的特质

在Scala中,特质中的方法并不需要一定是抽象的。 例如:

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

class Person(val Name:String){}

class Student(name:String) extends Person(name) with Logger{
  def learn(): Unit ={
    log(Name + "is learning ... ")
  }
} 

注意: Student 从 Logger特质得到了一个具体的 log方法实现。 用Java接口的话, 这是不可能做到的。

对上述样例进行反编译,查看java是怎么进行定义的:

// borey-zhu@AWS-TEST-DT:~/scala$ javap -p Logger.class 
// Compiled from "log.scala"
public interface Logger {
  // log 方法带有具体的实现,会生成对应的静态方法
  public static void log$(Logger, java.lang.String); 
  public void log(java.lang.String);
  public static void $init$(Logger);
}

// borey-zhu@AWS-TEST-DT:~/scala$ javap -p Student.class 
// Compiled from "log.scala"
public class Student extends Person implements Logger {
  public void log(java.lang.String); 
  public void learn();
  public Student(java.lang.String);
}

带有特质的对象

在构造单个对象时, 你可以为它添加特质。 例如:


trait Logger{
  // 带一个什么都没有做的实现
  def log(msg: String): Unit = {}
}

trait ConsoleLogger extends Logger{
  // override 必须添加
  override def log(msg: String): Unit = {
    println(msg)
  }
}

class Person(val Name:String){}

class Student(name:String) extends Person(name) with Logger{
  def learn(): Unit ={
    log(Name + " is learning ... ")
  }
}

定义了一个Logger特质, log函数是一个什么都不做的实现,再定义一个标准的ConsoleLogger扩展字Logger进行重写log 方法; 下列定义一个带有特质的对象,例如:

scala> val s1 = new Student("borey") with ConsoleLogger  // 加入特质 ConsoleLogger
s1: Student with ConsoleLogger = $anon$1@18d11527

scala> s1.learn
borey is learning ... 

我们在构造对象的时候可以加入任意一个扩展自Logger特质。

叠加在一起的特质

你可以为类或对象添加多个互相调用的特质,这对于需要分阶段加工处理某个值的场景很有用。

以下是一个简单示例,添加带有时间戳日志的特质和 截断过于长的日志消息的特质:

trait Logger{
  def log(msg: String): Unit = {}
}
// 控制台输出
trait ConsoleLogger extends Logger{
  override def log(msg: String): Unit = {
    println(msg)
  }
}
// 带时间戳的特质
trait TimestampLogger extends Logger{
  override def log(msg: String): Unit = {
    super.log(new java.util.Date() + " " + msg)
  }
}
// 带消息截断的特质
trait ShortLogger extends Logger{
  val maxLen = 10
  override def log(msg: String): Unit = {
    super.log( if (msg.length < maxLen) msg else msg.substring(0, maxLen -3) + "...")
  }
}

class Person(val Name:String){}

class Student(name:String) extends Person(name) with Logger{
  def learn(): Unit ={
    log(Name + " is learning ... ")
  }
}

注意: 特质是有层级的,TimestampLoggerShortLogger 中的 super.log 方法是调用层级中的下一个特质的方法; 具体调用哪个特质的方法,要根据特质的添加顺序来决定。 一般来说,特质是从最后一个开始被处理。

下面来看下不同顺序叠加的特质对象展现的结果:

scala> val s1 = new Student("borey") with ConsoleLogger with ShortLogger with TimestampLogger
s1: Student with ConsoleLogger with ShortLogger with TimestampLogger = $anon$1@6d7740f0

scala> s1.learn
Tue Oct...

scala> val s2 = new Student("borey") with ConsoleLogger with TimestampLogger with ShortLogger
s2: Student with ConsoleLogger with TimestampLogger with ShortLogger = $anon$1@192b472d

scala> s2.learn
Tue Oct 17 11:29:42 GMT+08:00 2017 borey is...

根据变量 s1 和 s2, 调用learn展现出的不同结果可以看出,不同的叠加顺序会产生不同的结果(从右向左依次被调用), 以 s2 为例:

graph TD
    A[Student.learn 中的 log 方法调用] --> B[ShortLogger 的 log 方法] 
    B --> C[TimestampLogger 的 log 方法]
    C --> D[最后调用 ConsoleLogger 的 log 方法]

说明: 对特质而言, 你无法从源码中判断super.Method 会执行哪里的方法, 确切的方法依赖于使用这些特质的对象或类给出的顺序。 这使得super相比在传统继承关系中要灵活得多。

在特质中重写抽象方法

假如上述定义的Logger 并没有提供 log 方法的实现会怎样? 当我们再添加一个带时间戳的特质,该如何定义?

这时候必须给方法加上 abstract 关键字, 在使用时间戳特质的时候必须混入一个具体的 log 方法。 例如:

trait Logger{
  def log(msg: String)
}

trait TimestampLogger extends Logger{
  // 必须添加 abstract关键字, 告诉编译器 TimestampLogger 依旧为抽象的
  abstract override def log(msg: String): Unit = {
    super.log(new java.util.Date() + " " + msg)
  }
}

trait ShortLogger extends Logger{
  val maxLen = 15
  abstract override def log(msg: String): Unit = {
    super.log( if (msg.length < maxLen) msg else msg.substring(0, maxLen -3) + "...")
  }
}

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

class Person(val Name:String){}

class Student(name:String) extends Person(name) with ConsoleLogger{
  def learn(): Unit ={
    log(Name + " is learning ... ")
  }
}

申明叠加的对象使用:

scala> val s1 = new Student("borey")  with TimestampLogger with ShortLogger
s1: Student with TimestampLogger with ShortLogger = $anon$1@7164e28a

scala> s1.learn
Tue Oct 17 12:03:54 GMT+08:00 2017 borey is lea...

当做富接口使用的特质

特质可以包含大量工具方法, 而这些工具方法可以依赖一些抽象方法来实现。 例如Scala的Iterator特质就利用抽象的next和hasNext定义了几十个方法。

// 抽象方法和具体方法结合在一起使用
trait Logger{
  def log(msg: String)
  def info(msg: String)  = { log("INFO: "  + msg)}
  def warn(msg: String)  = { log("WARN: "  + msg)}
  def error(msg: String) = { log("ERROR: " + msg)}
}

// 可以任意使用处理消息的方法 info、warn、error
class A extends Logger{
  def show() = {
    info("A: hello scala")
  }

  // ......

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

在Scala中像这样在特质中使用具体和抽象方法是很普遍的。在Java中, 你需要声明一个接口和一个额外的扩展该接口的类。

特质中的字段

尝试在特质中定义字段,例如:

trait A_T{
  // 特质字段
  val AT_Field = 10
  // 特质虚拟字段
  val msg:String

  def show() {println(msg)}
}

class B extends A_T {
  // 类字段
  val B_Field  = 20
  // 必须提供特质msg字段
  val msg = "hello scala"
}

通过反编译来看下我们定义的类B:

// borey-zhu@AWS-TEST-DT:~/scala$ scalac TraitTest.scala 
// borey-zhu@AWS-TEST-DT:~/scala$ ls
// A_T.class  B.class  TraitTest.scala
// borey-zhu@AWS-TEST-DT:~/scala$ javap -p A_T.class 
// Compiled from "TraitTest.scala"
public interface A_T {
  public abstract void A_T$_setter_$AT_Field_$eq(int);
  public abstract int AT_Field();
  public abstract java.lang.String msg();
  public static void show$(A_T);
  public void show();
  public static void $init$(A_T);
}
// borey-zhu@AWS-TEST-DT:~/scala$ javap -p B.class
// Compiled from "TraitTest.scala"
public class B implements A_T {
  private final int B_Field;
  private final java.lang.String msg;
  private final int AT_Field;
  public void show();
  public int AT_Field();
  public void A_T$_setter_$AT_Field_$eq(int);
  public int B_Field();
  public java.lang.String msg();
  public B();
}
// borey-zhu@AWS-TEST-DT:~/scala$ 

通过上述示例反编译,可以得出:

  • 特质中定义字段可以是具体的值也可以是抽象的;
  • 特质中字段在子类中不是被继承的,而是简单的进行添加到子类中;
  • 特质中未被初始的字段在具体的子类中必须进行重写;

特质构造顺序

和类一样,特质也有构造顺序,由字段的初始化和其他特质中的语句构成。例如:

trait Logger{
  println("trait Logger")
  def log(msg: String) = {}
  def info(msg: String)  = { log("INFO: "  + msg)}
  def warn(msg: String)  = { log("WARN: "  + msg)}
  def error(msg: String) = { log("ERROR: " + msg)}
}

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

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

trait ShortLogger extends Logger{
  println("trait ShortLogger")
  val maxLen = 15
  override def log(msg: String): Unit = {
    super.log( if (msg.length < maxLen) msg else msg.substring(0, maxLen -3) + "...")
  }
}

class Person(val Name:String){
  println("class Person")
}

class Student(name:String) extends Person(name) with Logger{
  println("class Student")
  def learn(): Unit ={
    log(Name + " is learning ... ")
  }
}

class Student2(name:String) extends Person(name) with ConsoleLogger with TimestampLogger{
  println("class Student")
  def learn(): Unit ={
    log(Name + " is learning ... ")
  }
}

声明一个带叠加特质的Student对象和一个Student2对象:

scala> val s1 = new Student("borey") with ConsoleLogger with TimestampLogger with ShortLogger
class Person
trait Logger
class Student
trait ConsoleLogger
trait TimestampLogger
trait ShortLogger
s1: Student with ConsoleLogger with TimestampLogger with ShortLogger = $anon$1@76d7881e

scala> s1.learn
Wed Oct 18 17:35:15 GMT+08:00 2017 borey is lea...

scala> val s2 = new Student2("borey")
class Person
trait Logger
trait ConsoleLogger
trait TimestampLogger
class Student
s2: Student2 = Student2@15a3b42

scala> s2.learn
Wed Oct 18 17:35:41 GMT+08:00 2017 borey is learning ... 

根据上述的样例,结果得出:

  • 首先调用超类的构造器;
  • 特质构造器在超类构造器之后,类构造器之前执行;
  • 每个特质当中,父特质构造器先被构造;
  • 特质由左向右构造,方法由右向左执行;
  • 所有特质构造完成,再进行子类构造;
  • 带叠加特质对象的构造在类完成构造之后,再由左向右进行带叠加的特质构造。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值