特质trait

特质(trait)是scala里代码服用的基础单元。特质封装了方法和字段的定义,并可以通过“混入”到类中重用它们。与类的继承时每个类都只能继承唯一的超类不同,类可以混入任意多个特质。特质的定义除了使用关键字trait之外,与类定义无异,如代码1-1

代码1-1

trait Bird {
  def fly = println("鸟飞翔")
  def singing
}

这个特质名为Fish,它没有声明超类,因此和类一样,有个默认的超类AnyRef。它定义了一个具体方法fly,也定义了一个抽象方法singing,等待被混入的类实现。一旦特质被定义了,就可以使用extends或with关键字,把它混入到类中,scala的混入特质并不是继承它们,将在本文的后面说明。

模拟一个场景,一个机器人继承了人类,人会说话,而机器人想要继续继承鱼的游泳、和鸟的飞翔和唱歌,需要用到特质,如代码1-2,Rebot类最先开始继承Person类,接着混入Fish特质和Bird特质,因此,机器人除了能工作,还能模拟鸟飞翔

代码1-2

scala> class Person {
  def say = println("人说话")
}

trait Fish {
  def swim = println("鱼游泳")
}

trait Bird {
  def fly = println("鸟飞翔")
  def singing
}

class Rebot extends Person with Fish with Bird {
  def work = println("机器人工作")
  def singing = println("机器人唱歌")
}
defined class Person
defined trait Fish
defined trait Bird
defined class Rebot

scala> val rebot = new Rebot()
rebot: Rebot = [email protected]

scala> rebot.work机器人工作

scala> rebot.fly
鸟飞翔

scala> rebot.singing
机器人唱歌

scala> val fish: Fish = rebot
fish: Fish = [email protected]

scala> fish.swim
鱼游泳

另外,因为Rebot这个类混入了Fish特质,所以可以用Fish的变量去接收Rebot对象,特质有点类似Java的抽象类,可以声明抽象方法和具体方法,但是Java的一个类只能继承一个抽象类,而scala一个类却可以混入多个特质,另外,特质不能带有任何类参数。代码1-3是合法的,而代码1-4则会报错

代码1-3

class Cat(color: String, age: Int)

代码1-4

trait Cat(color: String, age: Int)

如果想在特质里加入参数时,可如代码1-5这样做

代码1-5

scala> trait Cat {  val color: String  val age: Int  def cry = println("小猫喵喵叫")  def printColor = println("小猫的颜色是" + color)  def printAge = println("小猫的年龄是" + age + "岁")}defined trait Cat

scala> val cat = new {      val color = "白色"      val age = 1    } with Catcat: Cat = [email protected]

scala> cat.printColor小猫的颜色是白色

scala> cat.printAge小猫的年龄是1岁

scala> cat.cry小猫喵喵叫

Ordered特质

对象的比较是程序里常见的操作,比方定义一个有理数对象,用户可能调用<或者>=来判断两个对象之间的大小关系,如果没有Ordered特质之前,我们一般会像代码1-6这样做,Rational类是有理数类,类参数n代表分子,类参数d代表分母,私有成员g是n和d的最大公约数,我们会定义操作符来判断两个有理数对象的大小关系

代码1-6

scala> class Rational(n: Int, d: Int) {
  require(d != 0)
  private val g = gcd(n, d)
  val number = n / g
  val denom = d / g
  override def toString = number + "/" + denom
  private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
  def <(that: Rational) = number * that.denom < that.number * denom
  def >(that: Rational) = that < this
  def ==(that: Rational) = (number == that.number && denom == that.denom)
  def <=(that: Rational) = this < that || this == that
  def >=(that: Rational) = this > that || this == that
}
defined class Rational

scala> val x = new Rational(1, 2)
x: Rational = 1/2

scala> val y = new Rational(1, 3)
y: Rational = 1/3

scala> x > y
res11: Boolean = true

通过定义符号,我们可以比较对象间的大小关系,但scala专门提供了一个特质解决这样的问题,这个特质就是Ordered,Ordered特质让你仅仅只实现一个方法compare,使你的类可以使用>、<、>=、<=全套的比较方法,如代码1-7

代码1-7

scala> class Rational(n: Int, d: Int) extends Ordered[Rational] {
  require(d != 0)
  private val g = gcd(n, d)
  val number = n / g
  val denom = d / g
  override def toString = number + "/" + denom
  private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
  def compare(that: Rational) = number * that.denom - that.number * denom
}
defined class Rational

scala> val a = new Rational(1, 3)
a: Rational = 1/3

scala> val b = new Rational(1, 5)
b: Rational = 1/5

scala> a > b
res12: Boolean = true

scala> val c = new Rational(1, 2)
c: Rational = 1/2

scala> val d = new Rational(2, 5)
d: Rational = 2/5

scala> c < d
res13: Boolean = false

scala> val e = new Rational(1, 3)
e: Rational = 1/3

scala> val f = new Rational(1, 5)
f: Rational = 1/5

scala> e >= f
res14: Boolean = true

compare主要返回两个有理数对象做计算最后的结果,如果结果大于0则代表前一个对象大于后一个对象,如果结果小于0则代表前一个对象小于后一个对象,如果结果等于0代表两个对象相等

特质用来做可堆叠的改变

举个例子,思考一下对一个整数队列堆叠改动。队列有两种操作:put,把整数放入队列,和get,从队列取出整数,队列是先进先出的,因此get应该依整数进入队列时的顺序把它们取出来。

假设有一个类实现了这样的队列,你可以定义特质执行如下的改动:

  • Doubling:把所有放入到队列的数字加倍。
  • Incrementing:把所有放入到队列的数字增值
  • Filtering:从队列中过滤掉负数

这三种特质代表了改动,因为它们改变了原始队列的行为而并非定义了全新的队列。这三种同样也是可堆叠的。

代码1-8为抽象的IntQueue类,put方法把整数添加到队列中,get方法返回并移除开头的整数,length返回队列的长度

abstract class IntQueue {
  def get(): Int
  def put(x: Int)
  def length(): Int
}

代码1-9的BasicIntQueue类是抽象类IntQueue的实现

代码1-9

class BasicIntQueue extends IntQueue {
  private val buf = new scala.collection.mutable.ArrayBuffer[Int]
  def get() = buf.remove(0)
  def put(x: Int) = buf += x
  def length() = buf.length
}

代码1-10是运行时的样子:

代码1-10

scala> val queue = new BasicIntQueue
queue: BasicIntQueue = [email protected]

scala> queue.put(-1)

scala> queue.put(2)

scala> queue.put(3)

scala> queue.length()
res3: Int = 3

scala> queue.get()
res4: Int = -1

scala> queue.get()
res5: Int = 2

scala> queue.get()
res6: Int = 3

现在用特质改变它的行为,代码1-11展示了过滤掉负数,Filtering做了两件事情,第一件是它定义了超类IntQueue,这个定义意味着它只能混入扩展了IntQueue,因此可以把Filtering混入到BasicIntQueue,第二件事情是特质在声明为抽象的方法中有一个super的调用。这种调用对于普通的类来说是非法的,执行时必然失败。然而对于特质来说,则能调用成功。因为特质的super调用是动态绑定的。特质Filtering的super调用将直到被混入另一个特质或类之后,有了具体的方法定义时才工作。

这种安排对于实现可堆叠改动的特质来说是常常要用到的,为了告诉编译器你的目的,你必须在这种方法打上abstract override的标志。这种标识符的组合仅在特质成员的定义中被认可,在类中不行,它意味着特质碧玺混入某个具有期待方法的具体定义的类中。

代码1-11

trait Filtering extends IntQueue {
  abstract override def put(x: Int) = if (x >= 0) super.put(x)
}

代码1-12中,queue变量只是简单地指明了一个类并混入一个特质,可以用这样的形式来替代命名类,联系代码1-11和代码1-12,我们将-1、2、3依次放入队列,但最后返回的队列长度只有2,因为-1被过滤掉了

代码1-12

scala> val queue = new BasicIntQueue with Filtering
queue: BasicIntQueue with Filtering = [email protected]

scala> queue.put(-1)

scala> queue.put(2)

scala> queue.put(3)

scala> queue.length()
res3: Int = 2

scala> queue.get()
res4: Int = 2

scala> queue.get()
res5: Int = 3

我们再加入两个特质,如代码1-13

代码1-13

trait Doubling extends IntQueue {
  abstract override def put(x: Int) = super.put(2 * x)
}

trait Incrementing extends IntQueue {
  abstract override def put(x: Int) = super.put(x + 1)
}

代码1-14,我们可以看到,queue最长的长度为3,然而我们却放入了4个整数,两个负数两个正数,并且返回的数跟我们原先放入的数不同,越靠近右侧的特质越先起作用。Incrementing最先调用,对所有放入的数加1,接着就是Filtering,我们放入的-10+1后为-9,依旧小于0,所有我们放入的整数只有-1、2、3能到达Doubling,在到达Doubling,3个数经过前两个特质的转化后,变为0、3、4,经过Doubling后,每个数乘以2,为0、6、8,最后放入队列的就是0、6、8。

代码1-14

scala> val queue = new BasicIntQueue with Doubling with Filtering with Incrementing
queue: BasicIntQueue with Doubling with Filtering with Incrementing = [email protected]

scala> queue.put(-10)

scala> queue.put(-1)

scala> queue.put(2)

scala> queue.put(3)

scala> queue.length()
res10: Int = 3

scala> queue.get()
res11: Int = 0

scala> queue.get()
res12: Int = 6

scala> queue.get()
res13: Int = 8

特质是一种继承多个类似于类的结构的方式,但是它与许多语言中的多重继承有很重要的差别。其中的一个尤为重要:super的解释。对于多重继承来说,super调用导致的方法调用可以在调用发生的地方明确决定。而对于特质来说,方法调用是由类和被混入到类的特质的线性化决定的。

时间: 2024-10-02 06:15:52

特质trait的相关文章

快学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关键字来添加额外的特质 带有具体实现的特质:

scala akka 修炼之路5(scala特质应用场景分析)

scala中特质定义:包括一些字段,行为(方法/函数/动作)和一些未实现的功能接口的集合,能够方便的实现扩展或混入到已有类或抽象类中. scala中特质(trait)是一个非常实用的特性,在程序设计中能够 更好的抽象现实.使程序更关注各自功能和更好的将程序拆分成多个特质模块,使程序具有更强的扩展性.熟悉java的同学.能够将特质理解为抽象类.可是scala中能够在一个类中同一时候混入多个特质(使用extends 或with).而java中一个类仅仅能继承一个抽象类,假设要实现多个抽象类就必需使用

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

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

Scala基础07:特质

多重继承的问题 多重继承产生菱形继承问题.解决多重继承可能导致的问题消耗的资源远比多重继承产生的价值高. 特质 Java8中新增一个特性:default method ,可以在interface中实现的方法.Scala特质类似Java 8的interface. Scala类只能继承一个父类,但可以由多个特质拓展而成.Scala不支持多重继承,取而代之的是特质.Trait可以作为工具方法混入到相关类中. Scala使用特质达到类似多重继承的效果.一个类可以扩展自一个或多个特质,一个特质可以被多个类

大数据Scala系列之特质

大数据Scala系列之特质,特质的定义除了使用关键字trait之外,与类定义无异. 特质用来在类之间进行接口或者属性的共享.类和对象都可以继承特质,特质不能被实例化,因此也没有参数. 一旦特质被定义了,就可以使用extends或者with在类中混入特质. 1 作为接口使用的特质特质的定义: trait Logger{//这是一个抽象方法,特质中未被实现的方法默认是抽象的,不需要abstract关键字修饰def log(msg:String)} 子类对特质的实现: class ConsoleLog

从零学scala(五)文件和正则表达式、特质

一:文件和正则表达式 读取行 import scala.io.Source         val lines = Source.fromFile("D://report_data2.txt","UTF-8").getLines()            for( i <- lines) println(i)//遍历每一行的数据 val array = Source.fromFile("D://report_data2.txt","

Scalaz(4)- typeclass:标准类型-Equal,Order,Show,Enum

Scalaz是由一堆的typeclass组成.每一个typeclass具备自己特殊的功能.用户可以通过随意多态(ad-hoc polymorphism)把这些功能施用在自己定义的类型上.scala这个编程语言借鉴了纯函数编程语言Haskell的许多概念.typeclass这个名字就是从Haskell里引用过来的.只不过在Haskell里用的名称是type class两个分开的字.因为scala是个OOP和FP多范畴语言,为了避免与OOP里的type和class发生混扰,所以就用了typeclas

Spark小象学院笔记

---小象学院陈超视频教程笔记------陈超讲 第一节 Scala基础与实践 基于JVM的FP+OO 静态类型 和Java互操作 函数式编程和面向对象的结合,纯静态的语言. 解释器(interpreter) 值与变量(val & var) 函数(Function) 1.常量 val 2.变量 var 3.main函数要定义在object里面 实例: object Basic{ def hello(name : String): String ={ "Hello :" + na

Scala入门到精通——第二十四节 高级类型 (三)

作者:摆摆少年梦 视频地址:http://blog.csdn.net/wsscy2004/article/details/38440247 本节主要内容 Type Specialization Manifest.TypeTag.ClassTag Scala类型系统总结 在scala中,类(class)与类型(type)是两个不一样的概念.我们知道类是对同一类型数据的抽象,而类型则更详细. 比方定义class List[T] {}, 能够有List[Int] 和 List[String]等详细类型