trait的功能类似于Java中的interface,但是trait也兼具abstract class的特征,它提供的功能要远比interface和abstract class多很多,以下主要是通过对比trait和interface, abstract class的用法来了解trait的功能。
trait的第一个功能是定义一些抽象方法,使得继承它的类实现这些方法,这和Java的interface相同。但是,interface只能定义方法的prototype, 不能实现这些方法,而trait可以实现它所定义的方法,甚至是所有方法。从这个意义上来说,trait又有点儿像一个普通的class,但是比class更灵活,一个子类只能extends一个父类,但是一个子类可以with多个trait。如果每个trait的方法都有实现,这就实现了Java中不能同时extends多个class的问题。
trait可以extends或with其他traits。
一旦继承了traits,要么声明为abstract class, 要么实现trait的所有方法和域。
trait Animal {
def eat(food: String) // 抽象方法,带一个参数
def run(legs: Int): Unit // 抽象方法, 带一个参数和一个返回值,Unit可省略
def sleep = { //concrete method with no arguemtns
println("Hello, I'm sleeping. Do not interrupt me!")
}
}
二者共性在于都可以定义方法,即实现方法,也可以定义变量。不同的是,scala对var和val类型的变量处理方式不同, 是否有赋值也有所区别。
val类型的变量必须在声明时赋值,子类需要override父类的这个变量值时,必须显式的声明位override。var类型的变量如果在声明时没有被赋值,就被认为是abstract类型,那么子类继承该trait后,就必须赋值;如果var类型的变量在声明时有初始值,那么子类可以不重新赋值,也可以赋为其他值,此时不需要override,因为它是variable的。
例如:
trait Animal {
val legs = 4 // concrete
var liveArea: String // abstract
var species = "Crawler" // concrete
def run(legs: Int): Unit
...
}
class Bird extends Animal {
override val legs = 2
var liveArea = "everywhere" // 注意,var关键字不能少!
def run(legs: Int): Unit = {
println(s"I can run with $legs legs, and I can also fly")
}
...
}
trait有三种方式限制哪些class可以继承它,这是Java无法提供的功能。
- 通过显式定义子类必须要继承哪些类才可以继承该trait,prototype如下:
trait MyTrait {
this: BaseType =>
....
}
任何要继承MyTrait的类,必须是BaseType的子类。
例如:
trait Animal {
def eat
}
trait Fish {
this: Animal =>
}
class GoldenFish extends Animal with Fish {
def eat = { ... }
}
如果GoldenFish直接继承Fish, 会提示如下错误信息:
test.scala:24: error: illegal inheritance;
self-type GoldenFish does not conform to Fish’s selftype Fish with Animal
class GoldenFish extends Fish {
^
one error found
1.通过定义子类必须要实现的方法类限制可以继承的子类
prototype如下:
MyTrait Fish {
this: {def swim } =>
def eat
}
任何要继承Fish的类,必须实现swim方法。例如:
class GoldFish extends MyTrait {
def swim = { println("I'm swimming") }
def eat = { println("what can i eat?") }
}
这个trait的一个特点,可以在创建类实例时将trait mixin。例如我们有如下的trait:
trait Logging {
println("start logging")
def log(msg: String)
}
class EmptyClass {
def log(msg: String) = {}
}
val ec = new EmptyClass() with Logging
当执行这行代码后,会打印 “start logging”信息,原因是在创建EmptyClass实例时,Logging自动被继承,构造函数被自动调用。
这对我们添加log信息很有帮助。
规则如下:
如果class只继承一个trait, 那么直接用extends;
如果class继承一个class和一个或多个trait, 那么class用extends, trait用with;
如果class继承多个trait, 第一个trait用extends,其他的用with;
也就是有class优先将extends给该class, with给trait。