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 被构造完毕,子类才进行构造。

你可能感兴趣的:(Scala详解——Trait)