Scala讲座:混入多重继承、类型层次和集合

本文节选自最近在日本十分流行的Scala讲座系列的第四篇,由JavaEye的fineqtbull翻译。本系列的作者牛尾刚在日本写过不少有关Java和Ruby的书籍,相当受欢迎。

序言

上一次对比Java说明了Scala的特点和类定义的方法。这一次将更加深入一点,还是以迷你旅行的方式探险一下Scala的语法特点。

接下来介绍一下用代替Java接口的特征(Trait)来实现的混入(mix-in)多重继承、类型层次和集合。

用特征来实现混入(mix-in)式的多重继承

Scala里相当于Java接口的是特征(Trait)。Trait的英文意思是特质和性状(本文称其为特征),实际上他比接口还功能强大。与接口不同的是,它还可以定义属性和方法的实现。Scala中特征被用于服务于单一目的功能模块的模块化中。通过混合这种特征(模块)群来实现各种应用程序的功能要求,Scala也是按照这个构想来设计的。

一般情况下Scala的类只能够继承单一父类,但是如果是特征的话就可以继承多个,从结果来看就是实现了多重继承。这可以被认为是同Ruby模块基本相同的功能。就看一下下面的例子吧。为了辨认方便,此后的特征名称前都加上前缀字母T。特征既可以继承类也可以继承其他特征。

 
 
 
 
  1. class Person ; //实验用的空类,如果使用了上一次的Person类,则下面的  
  2. //PianoplayingTeacher类就需要构造参数了  
  3. trait TTeacher extends Person {  
  4. def teach //虚方法,没有实现  
  5. }  
  6. trait TPianoPlayer extends Person {  
  7. def playPiano = {println("I’m playing piano. ")} //实方法,已实现  
  8. }  
  9. class PianoplayingTeacher extends Person with TTeacher with TPianoPlayer {  
  10. def teach = {println("I’m teaching students. ")} //定义虚方法的实现  
  11. }  

如上所示,可以连着多个with语句来混合多个特征到一个类中。***个被继承源用extends,第二个以后的就用with语句。正如大家所知道的,可以生成实例的是非抽象(abstract)的类。另外请注意一下从特征是不可以直接创建实例的。

那么就实际运行一下吧。

 
 
 
 
  1. scala> val t1 = new PianoplayingTeacher  
  2. t1: PianoplayingTeacher = PianoplayingTeacher@170a650 
  3. scala> t1.playPiano  
  4. I’m playing piano.  
  5. scala> t1.teach  
  6. I’m teaching students.  

实际上如下所示,可以在创建对象时才将特征各自的特点赋予对象。

 
 
 
 
  1. scala> val tanakaTaro = new Person with TTeacher with TPianoPlayer {  
  2. | def teach = {println("I'm teaching students.")} }  
  3. tanakaTaro: Person with TTeacher with TPianoPlayer = $anon$1@5bcd91 
  4. scala> tanakaTaro playPiano  
  5. I’m playing piano.  
  6. scala> tanakaTaro teach  
  7. I'm teaching students.  

用特征来方便地实现面向方面的编程

充分利用特征的功能之后,就能方便地实现现今流行的面向方面编程(AOP)了。

首先,用特征来声明表示基本动作方法的模块Taction。

 
 
 
 
  1. trait TAction {  
  2. def doAction  
  3. }  

接着作为被加入的方面,定义一下加入了前置处理和后置处理的特征TBeforeAfter。

 
 
 
 
  1. trait TBeforeAfter extends TAction {  
  2. abstract override def doAction {  
  3. println("/entry before-action"//doAction的前置处理  
  4. super.doAction // 调用原来的处理  
  5. println("/exit after-action"//doAction的后置处理  
  6. }  
  7. }  

通过上面的abstract override def doAction {}语句来覆盖虚方法。具体来说这当中的super.doAction是关键,他调用了TAction的doAction方法。其原理是,由于doAction是虚方法,所以实际被执行的是被调用的实体类中所定义的方法。

那么将实际执行的实体类RealAction作为TAction的子类来实现吧。

 
 
 
 
  1. class RealAction extends TAction {  
  2. def doAction = { println("** real action done!! **") }  
  3. }  

接着就执行一下。

 
 
 
 
  1. scala> val act1 = new RealAction with TBeforeAfter  
  2. act1: RealAction with TBeforeAfter = $anon$1@3bce70 
  3. scala> act1.doAction  
  4. /entry before-action  
  5. ** real action done!! **  
  6. /exit after-action  

仅仅这样还不好玩,接着为他定义一下别的方面,然后将这些方面加入到同一对象的方法中。接着定义一个将源方法执行两遍的方面。

 
 
 
 
  1. trait TTwiceAction extends TAction {  
  2. abstract override def doAction {  
  3. for ( i <- 1 to 2 ) { // 循环执行源方法的方面  
  4. super.doAction // 调用源方法doAction  
  5. println( " ==> No." + i )  
  6. }  
  7. }  
  8. }  

下面,将TBeforeAfter和TtwiceAction混合在一起后执行一下。

 
 
 
 
  1. scala> val act2 = new RealAction with TBeforeAfter with TTwiceAction  
  2. act2: RealAction with TBeforeAfter with TTwiceAction = $anon$1@1fcbac1 
  3. scala> act2.doAction  
  4. /entry before-action  
  5. ** real action done!! **  
  6. /exit after-action  
  7. ==> No.1 
  8. /entry before-action  
  9. ** real action done!! **  
  10. /exit after-action  
  11. ==> No.2 

伴随着原来方法的before/after动作一起各自执行了两次。接着将混入顺序颠倒后再试一下。

 
 
 
 
  1. scala> val act3 = new RealAction with TTwiceAction with TBeforeAfter  
  2. act3: RealAction with TTwiceAction with TBeforeAfter = $anon$1@6af790 
  3. scala> act3.doAction  
  4. /entry before-action  
  5. ** real action done!! **  
  6. ==> No.1 
  7. ** real action done!! **  
  8. ==> No.2 
  9. /exit after-action  

这样执行后,原来的实现方法被循环执行了两次,但是before/after则在循环以外整体只执行了一次。这是根据with语句定义的顺序来执行的,知道了这原理之后也就没有什么奇怪的了。Scala特性的如此混入顺序是和AspectJ的方面以及Spring的interceptor相同的。

这样不仅是before和after动作,只要更改了特征的实现就可以将各种方面动态地加入到原来的对象中去了,读者自己也可以尝试一下各种其他情况。

在Java中通过Decorator或Template Method模式来想尽办法实现的功能,在Scala中只要通过特征就可以轻松到手了。从这还可以延展开来,通过在原来的方法中插入挂钩的方法,即所谓的拦截者式面向方面的方法,就可以轻松地将各个方面通过特征来组件化了。

请读者如果想起Scala是怎样的强类型和静态化语言的话,那么就能够明白通过特征来加入新功能的特

点给他带来了多大的灵活性。

Scala的类型体系(基本类型)

Scala中可使用的基本数据都以类的形式被定义了,所以基本类型与用户定义类型可以认为是没有区别的。虽然这么说,Scala还是提供了与Java的数据类型相对应的类定义群(图 4-1)。这绝不是包装类,在编译后他们将被映射为Java的字节码,所以性能上是绝对没有问题的。

 

图 4-1与Scala基本类型相对应的类群

如下例程序所示,对于整数对象7可以响应各种消息(方法)。既可以执行toString方法来转换成字符串,又可以使用to这个执行Int => Range的方法。附带说一下,7 to 20相当于7.to(20),该方法的执行结果是Range(7,8, 9, … 19, 20)。对于该范围对象适用了foreach( (i)=>print(i) ),print _则与一个参数的匿名函数(i) => print(i)相当。

 
 
 
 
  1. scala> 7.toString  
  2. res2: java.lang.String = 7 
  3. scala> 7 to 20 foreach( print _ )  
  4. 7891011121314151617181920 

实际上,Scala在编译器自动引入的Predef单例对象中定义了为了兼容Java基础类型所存在的类型别名。例如boolean, char, byte, short, int, long, float, double被定义了,这些别名实际上是引用了Scala.Boolean,Scala.Char,Scala.Byte等Scala的类。可能的话,为了提高“Scala中说所有数据都是对象”这种意识,建议尽量一开始就使用Int、Boolean、Float等原来的类名。

不过,在Scala种并没有类型转换操作符,而是在所有类的基类Any中定义了具有同等功能的方法asInstanceOf[X]。用这方法就可以把类型转换为X了。Any类中同时还定义了相当于instanceof操作符的isInstanceOf[X]方法。

 

图 4-2Scala类层次的基本结构

特别是该类层次中Iterable下的集类型在函数式编程中大显身手。其中的可变(mutable)与非可变(immutable)两大系列的类层次基本上呈现出镜像关系,可以充分发挥出函数式语言功能的当然就是非可变集类型群了。

结束语

这一讲以迷你旅行的形式说明了一下Scala语法的特点,函数定义和函数式编程就卖个关子放到下一讲去吧。

【编辑推荐】

  1. Scala讲座:面向对象和函数式的特点总结
  2. Scala讲座:函数式编程处理树结构数据
  3. Scala讲座:编程的思考方法
  4. Scala讲座:将函数作为***类对象来处理
  5. Scala讲座:全局变量问题的解决
THE END