Scala程序及其Application特质

来源:岁月联盟 编辑:zhu 时间:2009-07-21

要执行Scala程序,你一定要提供一个有main方法(仅带一个参数,Array[String],且结果类型为Unit)的孤立单例对象名。任何拥有合适签名的main方法的单例对象都可以用来作为程序的入口点。代码4.3展示了一个例子:

  1. // 文件Summer.scala  
  2. import ChecksumAccumulator.calculate  
  3. object Summer {  
  4.  def main(args: Array[String]) {  
  5.   for (arg < - args)  
  6.    println(arg + ": " + calculate(arg))  
  7.  }  
  8. }  

代码 4.3 程序Summer

代码4.3单例对象的名字是Summer。它的main方法具有合适的签名,所以你可以把它用作程序。文件中的第一个语句是引用定义在前例中ChecksumAccumulator对象中的calculate方法。这个引用语句允许你在文件之后的部分里使用方法的简化名。如果你是Java程序员,你可以认为这种引用类似于Java 5引入的精通引用特性。然而Scala里的一个不同是,你可以从任何对象引用成员,而不只是单例对象。main方法体简单地打印输出每个参数和参数的校验和,用冒号分隔。

注意

Scala隐式引用了包java.lang和scala的成员,和名为Predef的单例对象的成员,到每个Scala源文件中。Predef,被放置在包scala中,包含了许多有用的方法。例如,当在Scala源文件中写pringln的时候,你实际调用了Predef的println。(Predef.pringln运转并调用Console.println,做实际的工作。)当你写assert,你是在调用Predef.assert。

要执行Summer应用程序,把代码4.3的代码放在文件Summer.scala中。因为Summer使用了ChecksumAccumulator,把ChecksumAccumulator的代码,包括代码4.1的类和代码4.2里它的伴生对象,放在文件ChecksumAccumulator.scala中。

Scala和Java之间有一点不同,Java需要你在跟着类命名的文件里放上一个公共类——如文件SpeedRacer.java里要放上类SpeedRacer——Scala里,你可以任意命名.scala文件,而不用考虑里面放了什么Scala类或代码。然而通常情况下如果不是脚本,推荐的风格是像在Java里那样按照所包含的类名来命名文件,这样程序员就可以通过查看文件名的方式更容易地找到类。这就是我们在本例中文件ChecksumAccumulator.scala和Summer.scala上使用的方式。

无论ChecksumAccumulator.scala还是Summer.scala都不是脚本,因为他们是以定义结束的。反过来说,脚本必然以一个结果表达式结束。因此如果你尝试以脚本方式执行Summer.scala,Scala解释器将会报错说Summer.scala不是以结果表达式结束的(当然前提是你没有在Summer对象定义之后加上任何你自己的表达式)。正确的做法是,你需要用Scala编译器真正地编译这些文件,然后执行输出的类文件。其中一种方式是使用scalac,Scala的基本编译器。输入:

  1. ___FCKpd___1nbsp;scalac ChecksumAccumulator.scala Summer.scala 

这将编译你的源文件,不过在编译完成之前或许会有一个可感知的停顿。原因是每次编译器启动时,都要花一些时间扫描jar文件内容,并在即使你提交的是新的源文件也在查看之前完成其他初始化工作。因此,Scala的发布包里还包括了一个叫做fsc(快速Scala编译器)的Scala编译器后台服务:daemon。你可以这样使用:

  1. ___FCKpd___2nbsp;fsc ChecksumAccumulator.scala Summer.scala 

第一次执行fsc时,会创建一个绑定在你计算机端口上的本地服务器后台进程。然后它就会把文件列表通过端口发送给后台进程去编译,后台进程完成编译。下一次你执行fsc时,后台进程就已经在运行了,于是fsc将只是把文件列表发给后台进程,它会立刻开始编译文件。使用fsc,你只需要在第一次等待Java运行时环境的启动。如果想停止fsc后台进程,可以执行fsc -shutdown来关闭。

不论执行scalac还是fsc命令,都将创建Java类文件,然后你可以用scala命令,就像之前的例子里调用解释器那样运行它。不过,不是像前面每个例子里那样把包含了Scala代码的带有.scala扩展名的文件交给它解释执行,scala程序用来“解释”Scala源文件的真正机制是,它把Scala源码编译成字节码,然后立刻通过类装载器装载它们,并执行它们。在这里你要给它包含了正确签名的main方法的孤立对象名。因此,你可以这样运行Summer应用程序:

  1. ___FCKpd___3nbsp;scala Summer of love 

你会看到两个命令行参数的校验和被打印出来:

  1. of: -213 
  2. love: -182 

Application特质Scala提供了一个特质,scala.Application,可以节省你一些手指的输入工作。尽管我们还没有完全提供给你去搞明白它如何工作的所有需要知道的东西,不过我们还是认为你可能想要知道它。代码4.4展示了一个例子:

  1. import ChecksumAccumulator.calculate  
  2. object FallWinterSpringSummer extends Application {  
  3.  for (season < - List("fall", "winter", "spring"))  
  4.   println(season +": "+ calculate(season))  
  5. }  

代码 4.4 使用Application特质

使用这个特质的方法是,首先在你的单例对象名后面写上“extends Application” 。然后代之以main方法,你可以把想要放在main方法里的代码直接放在单例对象的大括号之间。就这么简单。之后可以像对其它程序那样编译和运行。

这种方式之所以能奏效是因为特质Application声明了带有合适的签名的main方法,并由你的单例对象继承,使它可以像个Scala程序那样用。大括号之间的代码被收集进了单例对象的主构造器:primary constructor,并在类被初始化时被执行。如果你不明白所有这些指的是什么也不用着急。之后的章节会解释这些,目前可以暂时不求甚解。

继承自Application比写个显式的main方法要短,不过它也有些缺点。首先,如果想访问命令行参数的话就不能用它,因为args数组不可访问。比如,因为Summer程序使用了命令行参数,所以它必须带有显式的main方法,如代码4.3所示。第二,因为某些JVM线程模型里的局限,如果你的程序是多线程的就需要显式的main方法。最后,某些JVM的实现没有优化被Application特质执行的对象的初始化代码。因此只有当你的程序相对简单和单线程情况下你才可以继承Application特质。

图片内容