学习过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 的构造顺序:
- 调用父类(超类或基类)的构造器;
- Trait 构造器在父类构造器之后、类构造器之前执行;
- Trait 从左到右进行构造;
- 每个 Trait 当中,父Trait 先被构造;
- 如果多个 Trait 共有一个父Trait ,父Trait 不会被重复构造;
- 所有 Trait 被构造完毕,子类才进行构造。