中国领先的IT技术网站
|
|

Scala初学者学习资料:main(String[])

本文是一位美国开发者在08年初写的Scala学习笔记的第一部分,讲述Scala中的一些基础知识,尤其是main(String[])的用法。

作者:Eastsun来源:JavaEye博客|2009-07-08 09:32

Tech Neo技术沙龙 | 11月25号,九州云/ZStack与您一起探讨云时代网络边界管理实践


51CTO编辑推荐:Scala编程语言专题

你可能已经使用JAVA若干年了,或许JAVA是你接触编程来的第一门编程语言,或许是作为比C++更好的一个选择。不管怎么样,你已经适应了JAVA,并了解她的外观与内在,能够体会它的喜怒与哀乐(原文:You’re comfortable with Java, you know its ins and outs, its moods)。她就像你相处多年的女朋友,或许刚相识时那种激情已经不再,但你已经熟悉怎样才能让她开心。简单的说,如果你是一位工匠,则JAVA就是你干活的工具。

然而,你和JAVA的蜜月期过去了,你在编程语言的选择上更加务实了。只要有足够的理由,你就可以毫不犹豫去尝试一种新的语言,但JAVA还没有足够的缺陷让你下定决心离开她。此时,你听说了一个叫做Ruby的东东,它的简洁优雅以及巨大的威力给你留下深刻的印象。但你并不是那么确定去使用一个脚步语言来构建你的企业应用。动态类型以及TextMate确实都不错,但对于现实世界,你需要一个更坚实的支柱。作为一个实用主义者,你仍然坚持使用JAVA。

这时,好消息传来了。一门新的编程语言登上了舞台并在开发者的世界引起了一场风暴。Scala貌似具有你对一门编程语言所期待一切: 静态类型,编译为字节码,简洁且富有表现力的语法。你可能已经被它的一些例子给深深吸引了。它看起来和JAVA非常相似,但是剔除了JAVA中冗余的语法结构。Scala没有分号,没有public static void的方法修饰词,并且具有静态类型推断机制。

现在剩下的唯一问题就是你应该从何开始。你也许尝试过去Scala的网站查看相关知识,但是。。这一切看起来是那么的函数化:Lamdas,high-order functions,immutable state,recursion out the wazoo。突然让人感到前途未卜。

但是,JavaEE的难民们,不必担心。Scala确实是一门函数式的编程语言,但它同样也是命令式的和面向对象的。也就是说,除了书写那种晦涩难懂的函数式代码(原文: It means that you don’t have to write code with the sole purpose of pleasing Haskell Curry),你还有其它的选择。你可以书写可读性很强的代码,以便你一个星期之后还能看懂它。将你的代码给你那些只使用JAVA的同事看,他们甚至也能看懂它。只需要一个正确的引导,你就可以使用Scala来构建你的的JAVA应用。

导读:

如果符合上面所说的,那么这个Scala系列就是为你而准备的。我已经阅读过大量有关Scala的文章以及入门材料(如果你更喜欢函数式的编程,我强烈推荐你去看Alex Blewitt写的系列文章),但很少有文章是为了使JAVA开发比较轻松的转变到Scala来的。我个人缺乏FP(函数式编程)的经验,因此我肯定不会在文章中介绍如何将Scheme代码移植到Scala。该系列文章着重于类比Scala与JAVA,并说明为什么Scala比JAVA更好。

起步:

  1. object HelloWorld extends Application{  
  2.     println("Hello, World!")  

没有什么比用代码来获得知识更直接了,注意到上面的代码没有使用分号。当然,如果你愿意还是可以像在JAVA中一样使用它。但除非你想在同一行中书写多个语句,否则分号不是必须的。这个代码示例含义非常清楚,想必你已经看明白了它的功能。没错,就是向标准控制台输出一行"Hello, World!"。你可以将这些代码保存到一个后缀名为scala的文件中,然后使用scala编译器编译它,编译的结果是一个单独的class文件(译者注:这里原文似乎有错,我编译后事实上生成了两个class文件)。你可以像运行普通java文件那样使用java解释器来运行它,不过你需要注意一下classpath。最直接的方法是使用scala命令:

  1. scalac hello.scala   
  2. scala HelloWorld 

注意到"hello.scala"文件名没有?Scala并不像Java一样强制你在定义public class时,必须保存在文件名与类名相同的文件中。事实上Scala允许你像C++或Ruby一样,在同一个文件中你想定义多少class都可以。但我们最好还是遵循Java的命名规则,因此作为一个具有良好习惯的程序员,我们应该将上面的代码保存为"HelloWorld.scala"。

编辑器:

在刚开始接触Scala,选择一个正确的编辑器是个关键。就像你在使用Java中所了解的那样,IDE非常有用。但作为一门新生的编程语言,Scala目前还没有很好的IDE支持,不过这只是时间问题。在目前,你只有非常有限的选择:

◆Eclipse(有几个不太成熟的Scala插件)

◆Emacs

◆IntelliJ(只支持基本的语法高亮)

◆TextMate

◆VIM

◆jEdit

(译者注:我使用的是UltraEdit,自己配置一下,能够自动缩进与语法高亮以及编译运行,凑合着用了)

上面列的是几个主要的选项,更完整的列表可以去Scala安装目录下的misc/scala-tool-support/文件夹下查看。我个人推荐使用jEidt或者TextMate;如果你有冒险精神,可以去尝试Eclipse上的几个插件。Eclipse上的插件具有通常IDE所具有的一些功能(至少Beta版是这样),譬如语义高亮,代码自动完成,etc。但根据我的经验,这些插件都不够稳定以至于难以使用。Scala是一门比Java简洁多的语言,它对IDE的依赖性比Java小得多,因此这些都不是本质问题,你可以照样做的很好。

再来一个例子

  1. object HelloWorld2{  
  2.     def main(args:Array[String]) = {  
  3.        var greeting ="" 
  4.        for(i <- 0 until args.length){  
  5.            greeting += (args(i) + " ")          
  6.        }  
  7.        if(args.length > 0) greeting =greeting.substring(0, greeting.length -1 )  
  8.          
  9.        println(greeting)  
  10.     }  

(译者注: 可能有读者会奇怪greeting += (args(i) + " ")这段代码为什么要用括号,注意,虽然与习惯不同,但这里的括号是必须的。因为在Scala里面,运算符的优先级由运算符第一个字符代表的运算符的优先级确定。就是说"+="的优先级与"+"的优先级一样,然后……自己想吧:-)。BTW,Scala里的运算符也是方法&对象。)

将它保存到HelloWorld2.scala,并使用如下命令编译运行:

  1. scalac HelloWorld2.scala   
  2. scala HelloWorld2 Hello, World! 

这次我们使用了不同的方式,通过命令行向程序传递参数,其运行的结果同样是在控制台输出"Hello, World"。这个程序比上一个复杂一些,首先定义了一个String类型的变量greeting,然后遍历一个数组,最后对String进行适当的处理(Scala专家肯定会建议使用Array#deepMkString(String)(类似于Ruby中的Array::join方法),这样确实没错。但我这里主要是为了介绍一些语言上的知识,而不是API的使用)。

首先应该注意的是,这里我们定义了一个main方法。在第一个例子中,我们仅仅继承了Application类,然后把其它的都交给默认构造函数来做。这样很好很简洁,但有两个问题:第一,我们没有办法向程序传递命令行参数;第二,对Scala新手来说,这样看起来有点魔幻。我将在后面的文章中揭开第一个例子中背后的把戏,现在你记住它就是了。

这个例子中的main方法是不是已经让你联想到了JAVA中的 public static void main?没错,Scala中的main就对应Java中的 public static void main。根据这个信息,有经验的程序员就可以通过思考知道更多关于Scala的知识。

一开始,你可能有这样的结论:Scala中的方法隐含就是public的。这基本正确,Scala中的方法默认为public,这意味着Scala中没有public方法修饰词(private与protected都有定义)。更进一步的,你会猜测:Scala中的方法默认为static的。然而,这次你的猜想不完全正确。

Scala并没有真正意义上的static属性。你越早认识到这一点,你就越容易理解这门语言。作为替代,Scala有专门的语法让你实现与使用sigleton模式(这正是object的含义)。我们实际上声明了一个具有实例方法main(译者注:注意,这个main方法并不是static的)的单实例类(singleton class)。我将会在以后详细说明这一点,目前你可以认为object就是一个只具有静态方法的类。(译者注:这个描述不甚准确)。

仔细的观察这个例子,我们还可以得到Scala中数组的语法,以及如何显式的指明一个变量的类型。让我们仔细研究一下声明main方法的这行代码:

  1. def main(args:Array[String]) = { 

这儿,args是一个Array[String]类型的方法参数。也就是说,args是一个String数组。在Scala中,Array是一个具有类型参数指明其元素类型的类(一个真正的类,而不是JAVA中那样)。与之对等的JAVA语法应该类似与下面这样子(假设Java中也有一个Array类):

  1. public static void main(Array< String> args) 

在Scala中,使用variable:Type的语法来指明变量类型。因此,如果我们想显式声明一个Int类型的变量,应该通过如下方式

  1. var myInteger:Int 

在例子中,我们实际上声明了一个String类型的变量greeting。然而,得益于Scala中的类型推断机制,我们并没有显式指明变量的类型。下面两种方式在语义上是等价的:

  1. var greeting ="" 
  2.  
  3. var greeting:String ="" 

在第一种方式中,我们显然知道greeting是一个String,Scala编译器也能够推断出这一点。这两个变量都是静态类型检查的,只不过第一种方式可以少写7个字符:-)

细心的读者可能还注意到了,例子中的main方法并没有指明返回类型。这是因为Scala同样可以推断出来。如果我们想显式的指明这点,可以像下面这样:

  1. def main(args:Array[String]):Unit = { 

如前面一样,类型在方法名之后,并用冒号隔开。顺便提一下,Unit是Scala中的一个类型,它表示"我不关心返回的究竟是什么东东"(原文:I-really-don't-care-what-I-return),你可以把它想象为JAVA中void类型与对象的混合物(译者注:但Unit不是void,而是一个实实在在的类,也就是说Unit类型的方法会返回一个Unit对象)。

数组的遍历:

  1. var greeting ="" 
  2. for(i <- 0 until args.length) {  
  3.     greeting += (args(i) + " ")  
  4. }  

(译者注: 目前Scala中这种方式遍历的性能相对低下,速度大概是JAVA中对应for的1/60。这个其实也不会造成太大的影响,因为耗时一般不在于for循环本身,而是循环体内的操作。更何况在Scala中用for的机会不多,有更多简单实用的方式去实现同样的操作)

这段代码比刚才那段稍微复杂点。我们一开始声明了一个String类型的变量greeting,然后使用了一个在Scala中不常用的for循环,这里有一些隐含的类型推断。有Ruby经验的知道在Ruby中的等价形式:

  1. for i in 0..(args.size - 1)  
  2.   greeting += args[i] + " " 
  3. end 

问题的关键在于,Scala的for循环语句需要一个Range对象,而这个Range对象是由RichInt的until方法建立的。我们可以把上面的代码分开写:

  1. val range =0.until(args.length)  
  2. for(i <- range) { 

注意,这里使用"val"而不是"var"声明变量。使用val,就如java中final的语义,指明range是一个常量。

在Scala中,我们可以通过多种不同的形式调用方法。在这个例子中,我们看到了"value methodName param"与"value。methodName(param)"这两种形式的语法是等价的。另外一个很重要的事情是,until方法是RichInt具有的,Int并没有该方法。这里存在一个隐式类型转换,将Int类型的0转换为scala。runtime。RichInt的一个实例。这里不去深究这个类型转换是怎样进行的,我们关心的是RichInt类确实有一个返回Rangle对象的until方法。

因此,事实上这段代码与JAVA中的下列代码在逻辑上是等价的:

  1. for (int i = 0; i < args.length; i++) { 

只不过在JAVA中是显式的遍历整个区间,而Scala中使用了一个Range对象。

可能你还注意到了,循环体中访问数组args元素使用的是圆括号"()",而不是我们习惯的方括号"[]"。

更好的遍历方式:

在JAVA 5中引入了一个foreach的语法,像下面这样:

  1. for(String arg: args) {  
  2.     greeting += arg + " ";  

这样更简洁明了。Scala中使用高阶函数(使用函数做参数的函数)实现类似的功能:

  1. args.foreach {arg =>  
  2.     greeting += (arg + " ")  

我们可以看到,Array类有一个foreach方法,这个方法将一个函数作为参数。foreach方法将会对Array里的每一个元素调用一次这个函数,并在调用的时候将这个元素作为函数参数传递给函数。这里参数arg没有指明类型,但由于我们是对一个String数组遍历,编译器能推断出它的类型是String。我们已经提到过,Scala中调用方法有多钟不同的方式。这里我们就省略了圆括号"()",同样我们也可以写成:

  1. args.foreach(arg => {  
  2.   greeting += (arg + " ")  
  3. }) 

Scala中我们可以使用更加简洁的书写方式:

  1. args.foreach(arg => greeting +=(arg + " ")) 

不错吧,现在我们将整个例子重写成:

  1. object HelloWorld2 {  
  2.   def main(args:Array[String]) = {  
  3.     var greeting = "" 
  4.     args.foreach(arg => greeting += (arg + " "))  
  5.     if (args.length > 0) greeting = greeting.substring(0, greeting.length - 1)  
  6.    
  7.     println(greeting)  
  8.   }  

Scala的内置类型:

因为Scala是基于JVM的,因此它从JAVA那儿继承了大量的API。这意味这你能够与JAVA交互使用。并且,你所写的Scala代码事实上使用的就是Java API。比如上面的HelloWorld2中我们使用了一个字符串变量greeting,这个变量事实上就是Java中的String类型。再比如,当你在Scala中声明一个整数类型(Int),编译器会自动将它转换为Java的基本类型int。

Scala还内置了一些隐式类型转换,当需要的时候编译器会自动进行(就像例子中的Int到RichInt类型的转换)。比如在Scala中将Array[String]类型传递给需要String[]类型参数的函数时,隐式类型转换就会发生。甚至Scala中的类型参数可以和Java中的泛型相互转化。简单的说,Scala就是一个披着不同语法外衣的Java。

结束

Scala不需要复杂高深的理论或学术知识。一个普通的开发者就可以在下一个企业WEB应用里面使用它。它具有JAVA所缺乏的简洁明了的语法,并且保留了JAVA的高效与可靠性。

【相关阅读】

  1. Scala创始人致信51CTO:全面解答Scala的泛型优势
  2. Scala的类型系统:取代复杂的通配符
  3. Scala的类型系统 比Java更灵活
  4. Java程序员,你为什么要关注Scala
  5. Scala编程语言
【责任编辑:杨赛 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

UNIX到Linux的移植

本书讲述怎样把UNIX环境下的应用程序移植到Linux环境上运行,是一本综合的开发和解决问题的参考手册 。本书详细描述了当前IT行业中被广泛应...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊
× CTO训练营(深圳站)