Scala详解——Trait

学习过Java的同学肯定知道Java中有接口(interface)的概念,它在JAVA编程语言中是一个抽象类型,是抽象方法的集合,Java中的类通过实现(implements)接口的方式,从而来继承接口中的抽象方法。但在Scala编程语言中,不存在接口(interface)的概念,那么为了实现与Java中的接口相似的功能,Scala语言给我们提供了一个类似的功能:Trait。

一、Trait 的定义

Scala Trait 相当于 Java 中的接口,但实际上它比接口的功能还要强大。

与接口不同的是,Trait 不仅可以定义抽象方法,还可以定义字段和方法的实现,然后将它们混合到类中进行重用。与类继承(每个类只能从一个父类继承)不同,一个类可以混合任意数量的 Trait。

一般情况下 Scala 类只能继承单一父类,但是如果是 Trait 的话就可以继承多个,单从结果上看是实现了多重继承。

Trait 的定义方式与类的定义方式类似,但它使用 trait 关键字,如下代码所示:

// 定义一个 trait
trait Flyable {
    // 声明一个抽象字段
    var maxFlyHeight:Int
    // 定义一个抽象方法
    def fly()
    // 定义一个具体的方法
    def breathe() {
        println("我能呼吸...")
    }
}

以上 trait 由一个字段和两个方法组成。fly() 方法没有定义方法的实现,breathe() 方法定义了方法的实现。子类继承 trait 可以实现 trait 中未实现的方法,因此 Scala Trait 与 Java 中的抽象类非常相似。

  • trait 既可以包含抽象成员,也可以包含非抽象成员。包含抽象成员时,不需要 abstract 关键字。
  • trait 可以使用 extends 继承其它的 trait,还可以继承类。
  • trait 的定义体就相当于主构造器,与类不同的是,不能给 trait 的主构造器提供参数列表,而且也不能为 trait 定义辅助构造器。

代码

// 定义一个 trait
trait Equal {
    def isEqual(x: Any): Boolean
    def isNotEqual(x: Any): Boolean = !isEqual(x)
}

// 定义一个 Point 类并继承 trait Equal
class Point(xc:Int, yc:Int) extends Equal {
    var x: Int = xc
    var y: Int = yc
    def isEqual(obj: Any) = obj.isInstanceOf[Point] && obj.asInstanceOf[Point].x == y
}

// 定义一个单例对象
object TestTrait {
    def main(args:Array[String]) {
      val p1 = new Point(2, 3)
      val p2 = new Point(2, 4)
      val p3 = new Point(3, 3)

      println(p1.isNotEqual(p2))
      println(p1.isNotEqual(p3))
      println(p1.isNotEqual(2))
    }
}

将以上代码保存文件名为 TestTraitDemo.scala 的文件中,编译运行该代码观察结果输出。

命令

# scalac TestTraitDemo.scala
# scala -classpath . TestTraitDemo

输出

true
false
true

二、把 trait 混入类中使用

可以使用 extends 或 with 关键字把 trait 混入类中。如果 trait 中包含抽象成员,则该类必须为这些抽象成员提供具体实现,除非该类被定义为抽象类。

// 定义 Bird 类并继承 trait Flyable
class Bird(flyHeight:Int) extends Flyable {
    // 重写 trait 的抽象字段
    var maxFlyHeight: Int = flyHeight
    // 重写 trait 的抽象方法
    def fly() {
        println("我可以飞" + maxFlyHeight + "米高。")
    }
}
scala> :load /opt/module/scala-2.11.0/mycode/Bird.scala # 将 Bird 代码保存为Bird.scala源文件
scala> val bird = new Bird(100) # 实例化一个bird对象
scala> bird.fly() # 调用 fly() 方法
我可以飞100米高。
scala> bird.breathe() # 调用 trait Flyable 中的 breathe() 方法
我能呼吸...

trait 也可以当做类型使用,即可以定义具有某种 trait 类型的变量,并使用任何混入了相应 trait 的类的实例进行初始化。

scala> val f:Flyable = new Bird(50)
scala> f.fly()  # 调用 Bird 类的方法fly()
我可以飞50米高。
scala> f.breathe()
我能呼吸...

当使用 extends 关键字混入 trait 时,相应的类就隐式地继承了 trait 的父类。如果想把 trait 混入到需要显式指定了父类的类里,则可以用 extends 指明待继承的父类,再用 with 混入 trait。

// 代码文件为/opt/module/scala-2.11.0/mycode/Bird.scala
// 定义一个 trait
trait Flyable {
    // 声明一个抽象字段
    var maxFlyHeight:Int
    // 定义一个抽象方法
    def fly()
    // 定义一个具体的方法
    def breathe() {
        println("我能呼吸...")
    }
}

// 定义一个动物类
class Animal(val category: String) {
    def info() {
        println("这动物的属性为:" + category)
    }
}

// 定义 Bird 类并继承 trait Flyable
class Bird(flyHeight:Int) extends Animal("Bird") with Flyable {
    // 重写 trait 的抽象字段
    var maxFlyHeight: Int = flyHeight
    // 重写 trait 的抽象方法
    def fly() {
        println("我可以飞" + maxFlyHeight + "米高。")
    }
}

命令&输出

// 将 Bird.scala 源代码加载到scala交互式环境中
scala> :load /opt/module/scala-2.11.0/mycode/Bird.scala
Loading /opt/module/scala-2.11.0/mycode/Bird.scala...
defined trait Flyable
defined class Animal
defined class Bird
scala> val b = new Bird(50)  // 实例化对象
b: Bird = Bird@27082746
scala> b.fly()  // 调用Bird中的fly()方法
我可以飞50米高。
scala> b.info() // 调用Animal中的info()方法
这动物的属性为:Bird
scala> b.breathe() // 调用trait Flyable 中的 breathe() 方法
我能呼吸...

三、把多个 Trait 混入类中使用

如果要混入多个 trait,可以连续使用多个 with。

在 上述Bird.scala中添加以下代码:

// 定义一个 trait HasLegs
trait HasLegs {
    // 定义一个抽象字段
    val legs: Int
    // 定义一个move实现方法
    def move(){
        println("我是用" + legs + "条腿走路!!!")
    }
}
// 将 Bird 类调整一下,如下:
class Bird(flyHeight:Int) extends Animal("Bird") with Flyable with HasLegs  {
    // 重写 trait 的抽象字段
    var maxFlyHeight: Int = flyHeight
    // 重写 trait HasLegs 中的legs抽象字段
    val legs = 2
    // 重写 trait 的抽象方法
    def fly() {
        println("我最高可以飞" + maxFlyHeight + "米。")
    }
}

命令&输出

scala> :load /opt/module/scala-2.11.0/mycode/Bird.scala
Loading /opt/module/scala-2.11.0/mycode/Bird.scala...
defined trait Flyable
defined trait HasLegs
defined class Animal
defined class Bird
scala> val b = new Bird(108)
b: Bird = Bird@cb0ed20
scala> b.info
这动物的属性为:Bird
scala> b.fly
我最高可以飞108米。
scala> b.move
我是用2条腿走路!!!

值类和通用 Trait

值类(Values Classes)是Scala中避免分配运行时对象的新机制。它包含一个主构造器,其中只有一个 val 参数。它只包含方法,不允许有var、val、嵌套类、trait 或对象。值类不能扩展出其他子类。但可以通过使用 AnyVal 扩展值类来实现。没有运行时开销的自定义数据类型的类型安全性。

我们以Weight, Height, Email, Age等为例。对于所有这些示例,都不需要在应用程序中分配内存。

不允许扩展 trait 的值类。为了允许值类扩展 trait ,引入了通用 trait ,它可以扩展任何 trait 。

代码演示

// 定义一个 trait Printable 并继承 Any 类
trait Printable extends Any {
    // 定义一个 print 实现方法
   def print(): Unit = println(this)
}
// 定义一个包装类 Wrapper 并继承 AnyVal 类和 trait Printable
class Wrapper(val underlying: Int) extends AnyVal with Printable

object ValueClassesTest {
   def main(args: Array[String]) {
      // 实例化 Wrapper
      val w = new Wrapper(3)
      // 下面语句实际上调用的是trait Printable 中的 print() 方法
      w.print()
   }
}

将上面的代码保存为 ValueClassesTest.scala 源文件中。下面进行编译运行操作,注意观察输出信息。

命令&输出

# scalac ValueClassesTest.scala
# scala -classpath . ValueClassesTest
Wrapper@3  # 输出 Wrapper 类的哈希值

四、什么时候使用 Trait

  • 如果某个字段或者方法不能进行重用,可以将它作为一个具体的类。
  • 如果某个字段或者方法可以在多个不相关的类中重用,可以将它作为一个Trait。只有 Trait 可以混合到类中。
  • 如果希望在Java 代码中被继承,则推荐使用抽象类。
  • 如果希望以编译的形式分发它,并且希望外部编写继承自它的类,则推荐使用抽象类。
  • 如果追求代码运行效率,则推荐使用普通类。

五、Trait 的构造顺序

Trait 也可以有构造器,由初始化的字段和其他 Trait 体中的语句构成。这些语句在任何混入该 Trait 的对象在构造时都会被执行。

Trait 的构造顺序:

  1. 调用父类(超类或基类)的构造器;
  2. Trait 构造器在父类构造器之后、类构造器之前执行;
  3. Trait 从左到右进行构造;
  4. 每个 Trait 当中,父Trait 先被构造;
  5. 如果多个 Trait 共有一个父Trait ,父Trait 不会被重复构造;
  6. 所有 Trait 被构造完毕,子类才进行构造。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容

  • 面向对象编程之类 定义一个简单的类 // 定义类,包含field以及方法 // 创建类的对象,并调用其方法 get...
    义焃阅读 780评论 0 2
  • 这篇讲义只讲scala的简单使用,目的是使各位新来的同事能够首先看懂程序,因为 scala 有的语法对于之前使用习...
    MrRobot阅读 2,912评论 0 10
  • 本文是对Scala语言面向对象编程的学习总结(下篇),共包括如下章节: 类的继承 抽象类 内部类 匿名类 特质(t...
    我是老薛阅读 330评论 0 3
  • 将trait作为接口使用 Scala中的Triat是一种特殊的概念首先我们可以将Trait作为接口来使用,此时的T...
    ZFH__ZJ阅读 487评论 0 3
  • 简介 scala是一门综合了面向对象和函数式编程概念的静态类型的编程语言。函数式编程以两大核心理念为指导:第一个理...
    盗梦者_56f2阅读 793评论 2 5