天天看点

10、Scala特质trait

本文介绍Scala中的trait特质。

1、基本知识

特质 (Traits) 用于在类 (Class)之间共享程序接口 (Interface)和字段 (Fields)。 它们类似于Java 8的接口。

即:特质里写了一些方法 和 字段,而这些是可以被所有类所共用、共享的。

这就像在类之间搭建了桥梁一样(或者打上了相同的标记),使得所有用了这个特质的类都可以有相同的特质中的方法和字段。

当然一个类可以使用多个特质,而不仅限于一个特质。

类和对象 (Objects)可以扩展特质,但是特质不能被实例化,因此特质没有参数。

特质使用关键词trait定义。

某种程度上看特质trait和类class很像,它们都不能独立运行,类必须被实例化,而特质必须被extends扩展(或继承)。

但特质比类更方便作为共享接口的地方就在于,它可以定义没有任何方法、只是约定了输入/输出参数类型的函数。

而这就可以作为类型/模式匹配的模版使用。

2、trait的定义方法

2.1 定义没有执行方法的trait

//in Scala

trait Cardetails{
	def details(d:String):String
}
           

2.2 定义有执行方法的trait

//in scala
trait detcar{
 def readdetails(d:String):String =
   Source.fromString(d).mkString
}
           

3、trait的使用方法

3.1 类使用extends来继承没有定义任何执行方法的特质

//in scala

//定义一个特质
trait Cardetails{
	def details(d:String):String
}

//定义一个类集成该特质,并在类中重写特质中的方法
class Cardet extends Cardetails {
  import scala.io.Source
  override def details(source:String) = {
	Source.fromString(source).mkString
  }
}

object car {
  def main(args:Array[String]){
	val c1 = new Cardet //实例化类
	println(c1.details("Car details are being displayed")) //调用类中的方法
	println(c1.isInstanceOf[Cardetails])  //验证下类是不是继承了这个特质
  }
}
           
10、Scala特质trait

3.2 一个类即继承了一个类,同时又继承了一个特质,使用extends  (类) with(特质)语法

//in scala
import scala.io.Source

//定义一个特质 detcar
trait detcar{
  def readdetails(d:String):String =
    Source.fromString(d).mkString +": this from trait"
}

//定义一个有参数的类Car
class Car(var cname:String, var cno:Int){
  def details = cname+" "+cno
}

//定义一个有参数的类Alto,该类继承了类Car,同时拥有特质detcar。
class Alto( cname:String, cno:Int,var color:String) extends Car(cname,cno) with detcar{

  //子类重写了父类的方法details,并且在重写的方法中引用了特质的方法
  override def details = {
    val det = readdetails(color)
    cname+"\n"+cno+"\n"+"Color:"+color +"\n" +det
  }
}

object Student {
  def main(args:Array[String]){
    val a1 = new Alto("Alto",34,"Black")
    println(a1.details)
  }
}
           
10、Scala特质trait

3.3 特质trait的多继承

特质可以继承特质,类也可以继承多个特质。

object TraitOrder extends App {

  trait Logger {
    println("Logger")
  }

 //一个trait 继承另一个trait
  trait FileLogger extends Logger {
    println("FileLogger")
  }

  trait Closable {
    println("Closable")
  }
  class Person{
    println("Constructing Person...")
  }

//一个类继承了父类,又继承了多个特质trait
  class Student extends Person with FileLogger with Closable {
    println("Constructing Student ...")
  }

  new Student
}
           

3.4 多个特质的继承顺序

准则:

  1. 如果有超类,则先调用超类的函数。
  2. 如果混入的trait有父trait,它会按照继承层次先调用父trait的构造函数。
  3. 如果有多个父trait,则按顺序从左到右执行。
  4. 所有父类构造函数和父trait被构造完之后,才会构造本类的构造函数。

这个准则依次对应到3.3中的代码为:

1、Student有父类Person

2、特质FileLogger有父特质 Logger

3、Student同时继承了两个父特质FileLogger 、Closable,继承顺序是从左到右执行

4、父类Person、特质Logger\FileLogger\Closable构造完后,Student类的构造函数才会开始

4、比较和选择使用trait 、abstract class的场景

在有可重复使用的方法、收集器的场景中,我们该怎么判断是使用trait 还是 abstract class呢?

以下4条基线可以参考:

  • 如果运行效率是你关注的一个指标,那么建议使用抽象类。 因为Traits 被编译为接口,可能会有轻微的性能开销。
  • 如果你需要继承一些JAVA代码,那么最好使用抽象类,因为特征没有 Java 模拟(analog),从特征继承就会很奇怪。
  • 如果行为不可重用,建议使用具体类。
  • 如果行为在多个不相关的类中重用,则可以使用特征。

继续阅读