1.什么是Trait ?
Traits are a fundamental unit of code reuse in Scala. A trait encapsulates
method and field definitions, which can then be reused by mixing them into
classes. Unlike class inheritance, in which each class must inherit from just
one superclass, a class can mix in any number of traits.
Traits是Scala中代码复用的基本单元
类似Java中的接口,但是可以定义method和field
2. 第一个Trait, 具有哲学家特征的青蛙,哈哈
package chapter12 trait Philosophical { def philosophise() = println("I consume memory, therefor I am !") } class Frog extends Philosophical { override def toString = "green" } object TestMain { def main(args: Array[String]) { val frog = new Frog frog.philosophise() //I consume memory, therefor I am ! } }
This trait is named Philosophical. It does not declare a superclass, so
like a class, it has the default superclass of AnyRef. It defines one method,
named philosophize, which is concrete.
默认继承 AnyRef
Once a trait is defined, it can be mixed in to a class using either the
extends or with keywords
用关键字: extends 或 with 将Trait混入类
You can use the extends keyword to mix in a trait; in that case you
implicitly inherit the trait’s superclass
当用extends 关键字时,该类隐式地继承了trait的父类
A trait also defines a type.
定义一个trait的同时也定义了一个新的类型
val philo: Philosophical = new Frog philo.philosophise()
3.显式声明父类,重载trait的方法
package chapter12 trait Philosophical { def philosophise() = println("I consume memory, therefor I am !") } class Cat class Frog extends Cat with Philosophical { override def toString = "green" override def philosophise() = println("It ain‘t easy to being " + toString) }
4.At this point you might philosophize that traits are like Java interfaces
with concrete methods, but they can actually do much more. Traits can, for
example, declare fields and maintain state. In fact, you can do anything in
a trait definition that you can do in a class definition, and the syntax looks
exactly the same, with only two exceptions.
Trait可以做class可以做的所有事情,只有两点例外
第一,trait 不能包含类参数,也就是说不能有构造函数
trait Philosophical(x: Int, y: Int) { def philosophise() = println("I consume memory, therefor I am !") } // 编译无法通过
第二,使用super调父类方法的时候,class中是静态绑定的,trait中是动态绑定的
The other difference between classes and traits is that whereas in classes,
super calls are statically bound, in traits, they are dynamically bound. If
you write “super.toString” in a class, you know exactly which method
implementation will be invoked. When you write the same thing in a trait,
however, the method implementation to invoke for the super call is undefined when you define the trait. Rather, the implementation to invoke will
be determined anew each time the trait is mixed into a concrete class. This
curious behavior of super is key to allowing traits to work as stackable modifications
关于可堆叠的更改(stackable modification)后面会详细说明
5.再看一个例子
package chapter12 class Point(val x: Int, val y: Int) trait Rectangular { def topLeft: Point def bottomRight: Point def left = topLeft.x def right = bottomRight.x def width = right - left } abstract class Component extends Rectangular class Rectangle (val topLeft: Point, val bottomRight: Point) extends Rectangular
这样所有的组件和矩形都有了Rectangular提供的几何方法
6. Ordered Trait
package chapter12 trait Ordered[T] { def compare(that: T): Int def > (that: T) = compare(that) > 0 def < (that: T) = compare(that) < 0 def >= (that: T) = (this > that) || (this == that) def <= (that: T) = (this < that) || (this == that) } class Rational(val n: Int, val d: Int) extends Ordered[Rational] { def compare(that: Rational) = this.n * that.d - this.d * that.n def equals(that: Rational) = (this.n * that.d - this.d * that.n) == 0 } object TestMain { def main(args: Array[String]) { val r1 = new Rational(3, 4) var r2 = new Rational(1, 2) println(r1 == r2) println(r1 > r2) println(r1 <= r2) } }
客户代码只需实现compare和equals, > < >= <=由trait实现
trait中的this在这里就是动态绑定的,同理super也是
7.接下来解释前面提到的 stackabe modification.首先实现一个整数队列
package chapter12 import scala.collection.mutable.ArrayBuffer abstract class IntQueue { def put(x: Int) def get(): Int } class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def put(x: Int) { buf += x} def get = buf.remove(0) }
8.来看一个神奇的魔术
package chapter12 import scala.collection.mutable.ArrayBuffer abstract class IntQueue { def put(x: Int) def get(): Int } class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def put(x: Int) { buf += x} def get = buf.remove(0) } trait Doubling extends IntQueue { abstract override def put(x: Int) { super.put(x * 2)} } object TestMain { def main(args: Array[String]) { class MyQueue extends BasicIntQueue with Doubling val queue = new MyQueue queue.put(1) queue.put(3) println(queue.get) println(queue.get) } }
两个println的输出是多少? 不再是1,3, 而是 2,6.
在不改变类BasicIntQueue代码的情况下,加上一个trait改变了BaistIntQueue的行为。这样的更改就叫做堆叠式更改。加一点东西来改变新的功能,而不是更改原有的功能,这是一个很重要的开发原则。
9.解释上面的代码
The Doubling trait has two funny things going on. The first is that it declares
a superclass, IntQueue. This declaration means that the trait can only be
mixed into a class that also extends IntQueue. Thus, you can mix Doubling
into BasicIntQueue, but not into Rational.
首先注意到的一点是trait Doubling 继承了一个class. 如果一个trait继承了一个class A, 那么trait只能被混入同样继承了class A的class。Scala依然要遵循单继承原则
The second funny thing is that the trait has a super call on a method
declared abstract. Such calls are illegal for normal classes, because they
will certainly fail at run time. For a trait, however, such a call can actually
succeed. Since super calls in a trait are dynamically bound, the super call
in trait Doubling will work so long as the trait is mixed in after another trait
or class that gives a concrete definition to the method
其次要注意的是 trait 中的 put方法被修饰为 abstract override
之所以为abstract方法,是因为这个方法调用了父类的抽象方法。这在class中是不允许的。调用了抽象方法的方法本身也应该是抽象的方法。允许再trait中这样做是因为:能够在运行时绑定这个抽象方法的实现。原文是:so long as the trait is mixed in after another trait or class that gives a concrete definition to the method。也就是说:只要在另一个实现了改方法的trait或class之后混入这个trait就可以
10. 可能你依然觉得不可思议。我们来剖析一下scala的编译器会如何解释对 MyQueue.put的调用
首先BasicIntQueue实现了put方法,如果没有混入trait,执行的应该是BasicIntQueue的方法
scala编译器在寻找最终执行那个方法的时候,也遵循后来居上的原则。后来我们混入了trait Doubling,它抽象地实现了put方法,真正执行的逻辑代码是Doubling的put,之不过在执行前,将抽象方法替换成了BasicIntQueue实现的具体方法
一句话: 真正执行的,是用具体方法替换后的trait中定义的逻辑