一、介绍
参考http://luchunli.blog.51cto.com/2368057/1705025
我们知道,如果几个类有某些共通的方法或者字段,那么从它们多重继承时,就会出现麻烦。所以Java被设计成 不支持多重继承,但可以实现任意多的接口。接口只能包含抽象方法,不能包含字段。
特质 trait 是Scala中特有的一项特点,不同于C#与Java,如果一定要拿C#与Java中的某项特点作对比的话,最接近的应该是接口,但是C#与Java中的接口是不允许带有方法实现的,而Scala中的Trait是可以带有方法实现的。这样做的好处需要某个trait就拿来用,而不需要重复实现接口。
所有的Java接口都可以作为Scala特质来使用。与Java一样,Scala类只能有一个超类,可以有任意数量的特质。
特质的定义使用保留字trait,具体语法与类定义类似,除了不能拥有构造参数
1 trait reset { 2 def reset (m : Int, n : Int) = if (m >= n) 1; 3 }
一旦特质被定义了,就可以混入到类中
1 class week extends reset {......}
当要混入多个特质时,利用with保留字
1 class week extends reset with B with C {......}
特质的成员可以是抽象的,而且不需要使用abstract声明。同样,重写特质的抽象方法无需给出override。但是,多个特质重写同一个特质的抽象方法需要给出override。
除了在类定义中混入特质外,还可以在特质定义中混入特质
1 trait reseting extends reset {......}
在对象构造时混入特质
1 val file = new month with reseting
特质的构造是有顺序的,从左到右被构造
1 /** 2 * @author lucl 3 */ 4 class Teacher { // 实验用的空类 5 println("===============I'm Teacher."); 6 } 7 8 trait TTeacher extends Teacher { 9 println("===============I'm TTeacher.") 10 def teach; // 虚方法,没有实现 11 } 12 13 trait TPianoTeacher extends Teacher { 14 println("===============I'm TPianoTeacher.") 15 def playPiano = { // 实方法,已实现 16 println("I'm playing piano."); 17 } 18 } 19 20 class PianoPlayingTeacher extends Teacher with TTeacher with TPianoTeacher { 21 println("===============I'm PianoPlayingTeacher.") 22 def teach = { // 定义虚方法的实现 23 println("I'm teaching students."); 24 } 25 } 26 27 object TraitOps { 28 def main (args : Array[String]) { 29 var p = new PianoPlayingTeacher; 30 p.teach; 31 p.playPiano; 32 } 33 } 34 35 /** 36 ===============I'm Teacher. 37 ===============I'm TTeacher. 38 ===============I'm TPianoTeacher. 39 ===============I'm PianoPlayingTeacher. 40 I'm teaching students. 41 I'm playing piano. 42 */
二、例子
我们的例子中定义了一个抽象类Aminal表示所有的动物,然后定义了两个trait Flyable和Swimable分别表示会飞和会游泳两种特征。
Aminmal的实现(定义了walk方法,实现了breathe方法)
1 abstract class Animal { 2 def walk(speed : Int); 3 4 def breathe () = { 5 println("animal breathes."); 6 } 7 }
Flyable和Swimable两个 trait的实现
1 /** 2 * @author lucl 3 * 有两个方法,一个抽象方法一个已实现方法 4 */ 5 trait Flyable { 6 def hasFather = true; 7 def fly; 8 } 9 10 package com.mtrait 11 12 /** 13 * @author lucl 14 * 只有一个抽象方法 15 */ 16 trait Swimable { 17 def swim; 18 }
我们定义一种动物,它既会飞也会游泳,这种动物是鱼鹰 FishEagle
1 /** 2 * @author lucl 3 */ 4 class FishEagle extends Animal with Flyable with Swimable { 5 /** 6 * 实现抽象类的walk方法 7 */ 8 override def walk(speed: Int) = { 9 println ("Fish eagle walk with speed : " + speed + "."); 10 } 11 12 /** 13 * 实现trait Flyable的方法 14 */ 15 override def fly = { 16 println("Fish eagle fly fast."); 17 } 18 19 /** 20 * 实现trait Swimable的方法 21 */ 22 override def swim { 23 println("Fish eagle swim fast."); 24 } 25 } 26 27 object FishEagle { 28 def main (args : Array[String]) { 29 val fish = new FishEagle; 30 fish.walk(100); 31 fish.fly; 32 fish.swim; 33 println("fish eagle has father ? " + fish.hasFather + "."); 34 // println(fish.swim); // 输出为() 35 36 println(); 37 val flyable : Flyable = fish; 38 flyable.fly; 39 40 val swimable : Swimable = fish; 41 swimable.swim; 42 } 43 } 44 45 /** 46 输出结果: 47 Fish eagle walk with speed : 100. 48 Fish eagle fly fast. 49 Fish eagle swim fast. 50 fish eagle has father ? true. 51 52 Fish eagle fly fast. 53 Fish eagle swim fast. 54 */
trait很强大,抽象类能做的事情,trait都可以做,它的长处在于可以多继承。
trait和抽象类的区别在于抽象类是对一个继承链的,类和类之前确实有父子类的继承关系,而trait则如其名字,表示一种特征,可以多继承。
在对象中混入trait
/** * 单独的日志模块 * 只是标识要记录日志,但没有明确定义如何记录日志 */ trait Logger { def log (msg : String) {} } /** * 记录日志的具体实现类 */ trait WriteLogger extends Logger { override def log (msg : String) = {println("WriteLogger : " + msg);} } /** * 需要执行的业务操作 */ trait Action { def doAction(action : String); } class TraitActionImpl extends Action { override def doAction(op : String) = println(op); } class LoggerActionImpl extends Action with Logger { override def doAction(op : String) = { println(op); // 如果确实需要日志功能但暂不清楚以何种形式记录日志时,可以采用该方法; // 当明确了记录日志的方式后,再通过如下在对象中混入trait实现。 log (op); } } /** * @author lucl */ object TraitOps { def main (args : Array[String]) { // println("===================aaaaaa========================"); // 类本身与记录日志Logger没有关系,但是在对象中混入trait的代码后,就具备了日志的功能 val actionA = new TraitActionImpl with WriteLogger; val op = "业务操作"; actionA.doAction(op); actionA.log(op); // println("===================bbbbbb========================"); // 类实现了Logger,但日志记录是空的操作 val loggerA = new LoggerActionImpl; loggerA.doAction(op); println("===================cccccc========================"); // 类实现了Logger,通过在类定义中混入trait实现了自己的记日志的功能 val loggerB = new LoggerActionImpl with WriteLogger; loggerB.doAction(op); } } /** 输出结果: ===================aaaaaa======================== 业务操作 WriteLogger : 业务操作 ===================bbbbbb======================== 业务操作 ===================cccccc======================== 业务操作 WriteLogger : 业务操作 */
三、总结
简单来说,Scala的trait就是类似于Java的接口。使一个类能实现多种功能。