天天看点

向Java开发者介绍Scala

scala结合了面向对象编程与函数编程思想,使用一种能够完全兼容java、可以运行在java虚拟机上的、简洁的语法。对于函数编程风格的支持,尤其是对于lambda表达式的支持,能够有助于减少必须要编写的逻辑无关固定代码,也许让它可以更简单的关注要面对的任务本身,而相对的java中对lamdba表达式的支持要到预定于2012年发布的javase8才会实现。本文就是对于scala介绍。

<a href="http://www.infoq.com/cn/vendorcontent/show.action?vcr=1445">flash builder 4.5高级版试用版免费高速下载</a>

<a href="http://www.infoq.com/cn/vendorcontent/show.action?vcr=1607">qclub(北京站)——《云计算与虚拟化在企业架构中的实施》(10月12日 晚7点)</a>

scala&gt; val columbus : int = 1492

columbus: int = 1492

我们刚刚声明了一个类型为int的变量,初始值为1492,就像我们在java里用语句int columbus = 1492;所做的一样。除了把类型放在变量之后这样一种反向的声明方式之外,scala在这里所表现出的不同是使用“val”来显性地把变量声明为不可变。如果我们想要修改这个变量:

请注意错误消息精确地指出了错误位于行的哪个位置。再尝试声明这个变量,但这一次用“var”,让其可变更。这样编译器拥有足够的智能来推断出1492是一个整数,你也就不需要指定类型了:

接下来,我们来定义一个类:

我们定义了一个类,名为employee,有三个不可变更的字段:name、age和company,各自有自己的缺省值。关键字“case”相当于java里的switch语句,只不过要更为灵活。它说明该类具有模式匹配的额外机制,以及其他一些特性,包括用来创建实例的工厂方法(不需要使用“new”关键字来构造),同样的也不需要创建缺省的getter方法。与java中不同的是,变量缺省下的访问控制是public(而不是protected),而scala为公开变量创建一个getter方法,并命名为变量名。如果你愿意,你也可以把字段定义成可变且/或私有(private)的,只需要在参数之前使用“var”(例如:case class person(private var name:string))。

我们再来用不同方式创建一些实例,看看其他的特性,像是命名参数和缺省参数(从scala2.8开始引入):

不过,下面的写法

是行不通的(可不是因为darth不是devcode的雇员!),这是由于构造函数在这个位置需要age作为参数,因为函数参数没有显性地进行命名。

现在我们再来看集合,这才是真正让人兴奋的地方。

有了泛型(java5以上),java可以遍历一个——比方说整数型列表,用下面这样的代码:

运行的结果是

number 1

number 2

number 3

scala对于可变集合和不可变集合进行了系统性地区别处理,不过鼓励使用不可变集合,也因此在缺省情况下创建不可变集合。这些集合是通过模拟的方式实现添加、更新和删除操作,在这些操作中,不是修改集合,而是返回新的集合。

与前面的java代码等价的scala代码可能像下面这样:

这里的“for”循环语法结构非常接近于java的命令式编程风格。在scala(以及java虚拟机上其他很多语言如:groovy、jruby或jpython)里还有另外一种方式来实现上面的逻辑。这种方式使用一种更加偏向函数编程的风格,引入了lambda表达式(有时也称为闭包——closure)。简单地说,lambda表达式就是你可以拿来当作参数传递的函数。这些函数使用参数作为输入(在我们的例子中就是“n”整型变量),返回语句作为函数体的最终语句。他们的形式如下

上面的例子中,函数体只有一条语句(println……),返回的是单位(unit,也就是“空结果”),也就是大致相当于java中的void,不过有一点不同的是——void是不返回任何结果的。

除了只打印出我们的数值列表以外,应该说我们更想做的是处理和变换这些元素,这时我们需要调用方法来生成结果列表,以便后面接着使用。让我们尝试一些例子:

最后的这一个变换“map”非常有用,它对列表的每一个元素应用闭包,结果是一个同样大小的、包含了每个变换后元素的列表。

我们在这里还想介绍最后的一个方法,就是“foldleft”方法,它把状态从一个元素传播到另一个元素。比如说,要算出一个列表里所有元素的和,你需要累加它们,并在切换元素的时候保存中间的计数:

作为第一个变量传递给foldleft的值0是初始值(也就是说在把函数用到第一个列表元素的时候total=0)。(total,element)代表了一个tuple2,在scala里这是一个二元组(就像要表示三维空间坐标,经常要用到tuple3(x,y,z)等等)。注意在合计时,scala的编程接口实际上提供了一个“sum”方法,这样上一条语句就可以写成:

最后,{ n =&gt; n + 10 }还可以简单地写成(_ + 10),也就是说如果输入参数只是用于你调用的方法,则不需要声明它;在我们的例子里,“n”被称为匿名变量,因为你可以把它用任何形式来代替,比如说“x”或者“number”,而下划线则表示一处需要用你的列表的每个元素来填补的空白。(与“_”的功能类似,groovy保留了关键字“it”,而python则使用的是“self”)。

在介绍了对整数的基本处理后,我们可以迈入下一个阶段,看看复杂对象集合的变换,例如使用我们上面所定义的employee类:

从这个五个元素的列表里,我们可以应用一个条件来过滤出应用匿名方法后返回值为true的雇员,这样就得到了——比方说属于devcode的雇员:

假设我们手头的allemployees集合是我们使用sql查询获得的结果集,查询语句可能类似于“select * from employees where company = ‘devcode’ ”。现在我们可以把list[employee]变换到以company名称作为键、属于该公司的所有员工的列表作为值的map类型,这样就可以把雇员按company来排序:

每一个列表已经作为一个值存入了(键——值)哈希表,为了示范如何进一步处理这些列表,可以设想我们需要计算每个公司的雇员平均年龄。

这具体意味着我们必须要计算每个列表的每个雇员的的“age”字段的和,然后除以该列表中雇员的数量。让我们先计算一下devcode:

回到我们的map (key:string -&gt;value:list[employee]),下面是个更加一般性的例子。我们现在可以归并并计算每个公司的平均年龄,要做的只是写几行代码:

这里的“case(key,value)”说明了scala提供的模式匹配机制是多么强大。请参考scala的文档来获取更多的信息。

到这里我们的任务就完成了。我们实现的是一个简单的map-reduce算法。由于每个公司雇员的归并是完全独立于其他公司,这个算法非常直观地实现了并行计算。

在后面的附录里给出了此算法的等价的实现,分为java版本和scala版本。

<a href="http://typesafe.com/stack/download" target="_blank">the typesafe stack.</a>

map reduce: java

mapreduce.scala: