鲁春利的工作笔记,好记性不如烂笔头
特质
Scala的trait 和Java 的Interface相比,可以有方法的实现。Scala的Trait支持类和Singleton对象和多个Trait混合(使用来自这些Trait中的 方法,而不时不违反单一继承的原则)。
Scala为Singleton对象的main定义了一个App trait类型,因此上面的例子可以简化为:
HelloWorld.scala object HelloWorld extends App { println("Hello World!"); }
这段代码就不能作为脚本运行,Scala的脚本要求代码最后以表达式结束。因此运行这段代码,需要先编译这段代码,然后再运行。
// 第一次直接运行,无任何输出 G:\Hadoop\scala-SDK\source>scala HelloWorld.scala // 进行编译 G:\Hadoop\scala-SDK\source>scalac HelloWorld.scala // 再次运行 G:\Hadoop\scala-SDK\source>scala HelloWorld Hello World!
注意: Scala提供了一个快速编译代码的辅助命令fsc (fast scala compliler) ,使用这个命令,只在第一次使用fsc时启动JVM,之后fsc在后台运行,这样就避免每次使用scalac时都要载入相关库文件,从而提高编译速度。
Scala不支持多重继承,取而代之的是特质,即class week extends month, year是不合法的;
说明:多重继承会产生菱形继承问题。
Scala使用特质达到类似多重继承的效果。
特质是Scala里代码复用的基础单元,封装了方法和字段的定义;
特质的定义使用保留字trait,具体语法与类定义类似,除了不能拥有构造参数;
trait reset { def reset (m : Int, n : Int) = if (m >= n) 1; }
一旦特质被定义了,就可以混入到类中;
class week extends reset {......}
当要混入多个特质时,利用with保留字;
class week extends reset with B with C {......}
特质的成员可以是抽象的,而且不需要使用abstract声明;
同样,重写特质的抽象方法无需给出override;
但是,多个特质重写同一个特质的抽象方法需要给出override;
除了在类定义中混入特质外,还可以:
在特质定义中混入特质
trait reseting extends reset {......}
在对象构造时混入特质
val file = new month with reseting
特质的构造是有顺序的,从左到右被构造,构造顺序为:
超类
父特质
第一个特质
第二个特质(父特质不重复构造)
类
简单示例:
/** * @author lucl */ class Teacher { // 实验用的空类 println("===============I'm Teacher."); } trait TTeacher extends Teacher { println("===============I'm TTeacher.") def teach; // 虚方法,没有实现 } trait TPianoTeacher extends Teacher { println("===============I'm TPianoTeacher.") def playPiano = { // 实方法,已实现 println("I'm playing piano."); } } class PianoPlayingTeacher extends Teacher with TTeacher with TPianoTeacher { println("===============I'm PianoPlayingTeacher.") def teach = { // 定义虚方法的实现 println("I'm teaching students."); } } object TraitOps { def main (args : Array[String]) { var p = new PianoPlayingTeacher; p.teach; p.playPiano; } } /** ===============I'm Teacher. ===============I'm TTeacher. ===============I'm TPianoTeacher. ===============I'm PianoPlayingTeacher. I'm teaching students. I'm playing piano. */
作为接口的trait代码使用:
我们的例子中定义了一个抽象类Aminal表示所有的动物,然后定义了两个trait Flyable和Swimable分别表示会飞和会游泳两种特征。
Aminmal的实现(定义了walk方法,实现了breathe方法):
package com.mtrait /** * @author lucl * 抽象类 */ abstract class Animal { def walk(speed : Int); def breathe () = { println("animal breathes."); } }
再看下Flyable和Swimable两个 trait的实现:
package com.mtrait /** * @author lucl * 有两个方法,一个抽象方法一个已实现方法 */ trait Flyable { def hasFather = true; def fly; } package com.mtrait /** * @author lucl * 只有一个抽象方法 */ trait Swimable { def swim; }
我们定义一种动物,它既会飞也会游泳,这种动物是鱼鹰 FishEagle,我们看下代码:
package com.mtrait /** * @author lucl */ class FishEagle extends Animal with Flyable with Swimable { /** * 实现抽象类的walk方法 */ override def walk(speed: Int) = { println ("Fish eagle walk with speed : " + speed + "."); } /** * 实现trait Flyable的方法 */ override def fly = { println("Fish eagle fly fast."); } /** * 实现trait Swimable的方法 */ override def swim { println("Fish eagle swim fast."); } } object FishEagle { def main (args : Array[String]) { val fish = new FishEagle; fish.walk(100); fish.fly; fish.swim; println("fish eagle has father ? " + fish.hasFather + "."); // println(fish.swim); // 输出为() println(); val flyable : Flyable = fish; flyable.fly; val swimable : Swimable = fish; swimable.swim; } } /** 输出结果: Fish eagle walk with speed : 100. Fish eagle fly fast. Fish eagle swim fast. fish eagle has father ? true. Fish eagle fly fast. Fish eagle swim fast. */
trait很强大,抽象类能做的事情,trait都可以做,它的长处在于可以多继承。
trait和抽象类的区别在于抽象类是对一个继承链的,类和类之前确实有父子类的继承关系,而trait则如其名字,表示一种特征,可以多继承。
在对象中混入trait:
package com.mtrait /** * 单独的日志模块 * 只是标识要记录日志,但没有明确定义如何记录日志 */ 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 : 业务操作 */
比Java的接口方便多了。