摘要在历史上Java; 平台一直属于面向对象编程的领域但是现在甚至 Java 语言的坚定支持者也开始注意应用程序开发中的一种新趋势函数编程在这个新的系列中Ted Neward 介绍了 Scala一种针对 JVM 将函数和面向对象技术组合在一起的编程语言在本文中Ted 将举例说明您为何应该花时间学习 Scala(例如并发)并介绍如何快速从中受益
对于我来说她的名字是 Tabinda (Bindi) Khan那是一段愉快的少年时光准确地说是在七年级她很美丽聪明而最好的是她常常因我的笨拙的笑话而乐不可支在七年级和八年级的时间里我们经常 出去走走(那时我们是这么说的)但到了九年级我们分开了文雅一点的说法是她厌倦了连续两年听到同样的笨拙的男孩笑话我永远都不会忘记她(特别是因为我们在高中毕业 周年聚会时再次相遇)但更重要的是我将永远不会失去这些珍贵的(也许有点言过其实)回忆
Java 编程和面向对象是许多程序员的 初恋我们对待它就像对待 Bindi 一样尊重和完全的爱慕一些开发人员会告诉您 Java 将他们从内存管理和 C++ 的炼狱中解救出来了其他一些人会告诉您 Java 编程使他们摆脱了对过程性编程的绝望甚至对于一些开发人员来说Java 代码中的面向对象编程就是 他们做事情的方式(嘿嘿如果这对我爸爸以及爷爷有用该多好!)
然而时间最终会沖淡所有对初恋的记忆生活仍然在继续感情已经变了故事中的主角也成熟了(并且学会了一些新笑话)但最重要的是我们周围的世界变了许多 Java 开发人员意识到尽管我们深爱 Java 编程但也应该抓住开发领域中的新机会并了解如何利用它们
关于本系列
Ted Neward 潜心研究 Scala 编程语言并带您跟他一起徜徉在这个新的 developerWorks 系列 中您将深入了解 Scala并在实践中看到 Scala 的语言功能在进行相关比较时Scala 代码和 Java 代码将放在一起展示但(您将发现)Scala 中的许多内容与您在 Java 编程中发现的任何内容都没有直接关联而这正是 Scala 的魅力所在! 毕竟如果 Java 代码可以做到的话又何必学习 Scala 呢?
我将始终爱着你 ……
在最近五年中对 Java 语言的不满情绪逐渐增多尽管一些人可能认为 Ruby on Rails 的发展是主要因素但是我要争辩的是RoR(被称为 Ruby 专家)只是结果而非原因或者可以更准确地说Java 开发人员使用 Ruby 有着更深刻更隐伏的原因
简单地说Java 编程略显老态了
或者更准确地说Java 语言 略显老态了
考虑一下当 Java 语言最初诞生时Clinton(第一位)在办公室中很少有人使用 Internet这主要是因为拨号是在家里使用网络的惟一方式博客还没有发明出来每个人相信继承是重用的基本方法我们还相信对象是为对世界进行建模的最好方法摩尔定律将永远统治着世界
实际上摩尔定律引起了行业内许多人的特别关注自 / 年以来微处理器技术的发展使得具有多个 内核 的 CPU 得以创造出来本质上是一个芯片内具有多个 CPU这违背了摩尔定律摩尔定律认为 CPU 速度将每隔 个月翻一倍在两个 CPU 上同时执行多线程环境而不是在单个 CPU 上执行标准循环周期这意味着代码必须具有牢固的线程安全性才能存活下来
学术界已经展开了围绕此问题的许多研究导致了过多新语言的出现关键问题在于许多语言建立在自己的虚拟机或解释器上所以它们代表(就像 Ruby 一样)到新平台的转换并发沖突是真正的问题所在一些新语言提供了强大的解决方案太多的公司和企业对 年前从 C++ 到 Java 平台的迁移仍记忆犹新许多公司都不愿意冒迁移到新平台的风险事实上许多公司对上一次迁移到 Java 平台仍心有余悸
了解 Scala
一种可伸缩语言
Scala 是一种函数对象混合的语言具有一些强大的优点
● 首先Scala 可编译为 Java 字节码这意味着它在 JVM 上运行除了允许继续利用丰富的 Java 开源生态系统之外Scala 还可以集成到现有的 IT 环境中无需进行迁移
● 其次Scala 基于 Haskell 和 ML 的函数原则大量借鑒了 Java 程序员钟爱的面向对象概念因此它可以将两个领域的优势混合在一起从而提供了显着的优点而且不会失去我们一直依赖的熟悉的技术
● 最后Scala 由 Martin Odersky 开发他可能是 Java 社区中研究 Pizza 和 GJ 语言的最着名的人GJ 是 Java 泛型的工作原型而且它给人一种 严肃 的感觉该语言并不是一时兴起而创建的它也不会以同样的方式被抛弃
Scala 的名称表明它还是一种高度可伸缩 的语言我将在本系列的后续文章中介绍有关这一特性的更多信息
下载并安装 Scala
可以从Scala 主页 下载 Scala 包截止到撰写本文时最新的发行版是 final它可以在 Java 安装程序版本 RPM 和 Debian 软件包 gzip/bz/zip 包中获得可以简单地将其解压到目标目录中而且可以使用源码 tarball 从头创建(Debian 用户可以使用 aptget install 直接从 Debian 网站上获得 版 版本具有一些细微的差异所以建议直接从 Scala 网站下载和安装)
将 Scala 安装到所选的目标目录中 — 我是在 Windows? 环境中撰写本文的所以我的目标目录是 C/Prg/scalafinal将环境变量 SCALA_HOME 定义为此目录将 SCALA_HOME\bin 放置于 PATH 中以便从命令行调用要测试安装从命令行提示符中激发 scalac version它应该以 Scala 版本 final 作为响应
函数概念
开始之前我将列出一些必要的函数概念以帮助理解为何 Scala 以这种方式操作和表现如果您对函数语言 — HaskellML 或函数领域的新成员 F# — 比较熟悉可以 跳到下一节
函数语言的名称源于这样一种概念程序行为应该像数学函数一样换句话说给定一组输入函数应始终返回相同的输出这不仅意味着每个函数必须返回一个值还意味着从一个调用到下一个调用函数本质上不得具有内蕴状态(intrinsic state)这种无状态的内蕴概念(在函数/对象领域中默认情况下指的是永远不变的对象)是函数语言被认为是并发领域伟大的 救世主 的主要原因
与许多最近开始在 Java 平台上占有一席之地的动态语言不同Scala 是静态类型的正如 Java 代码一样但是与 Java 平台不同Scala 大量利用了类型推断(type inferencing)这意味着编译器深入分析代码以确定特定值的类型无需编程人员干预类型推断需要较少的冗余类型代码例如考虑声明本地变量并为其赋值的 Java 代码如清单 所示
清单 声明本地变量并为其赋值的 Java 代码
class BrainDead {
public static void main(String[] args) {
String message = Why does javac need to be told message is a String? +
What else could it be if Im assigning a String to it?;
}
}
Scala 不需要任何这种手动操作稍后我将介绍
大量的其他函数功能(比如模式匹配)已经被引入到 Scala 语言中但是将其全部列出超出了本文的范围Scala 还添加许多目前 Java 编程中没有的功能比如操作符重载(它完全不像大多数 Java 开发人员所想象的那样) 具有 更高和更低类型边界 的泛型视图等与其他功能相比这些功能使得 Scala 在处理特定任务方面极其强大比如处理或生成 XML
但抽象概述并不够程序员喜欢看代码所以让我们来看一下 Scala 可以做什么
开始认识您
根据计算机科学的惯例我们的第一个 Scala 程序将是标准的演示程序 Hello World
Listing HelloScala
object HelloWorld {
def main(args: Array[String]): unit = {
Systemoutprintln(Hello Scala!)
}
}
使用 scalac Helloscala 编译此程序然后使用 Scala 启动程序(scala HelloWorld)或使用传统的 Java 启动程序运行生成的代码注意将 Scala 核心库包括在 JVM 的类路径(java classpath %SCALA_HOME%\lib\scalalibraryjar HelloWorld)中不管使用哪一种方法都应出现传统的问候
清单 中的一些元素对于您来说一定很熟悉但也使用了一些新元素例如首先对 Systemoutprintln 的熟悉的调用演示了 Scala 对底层 Java 平台的忠诚Scala 充分利用了 Java 平台可用于 Scala 程序的强大功能(事实上它甚至会允许 Scala 类型继承 Java 类反之亦然但更多信息将在稍后介绍)
另一方面如果仔细观察您还会注意到在 Systemoutprintln 调用的结尾处缺少分号这并非输入错误与 Java 平台不同如果语句很明显是在一行的末尾终结则 Scala 不需要分号来终结语言但是分号仍然受支持而且有时候是必需的例如多个语句出现在同一物理行时通常刚刚入门的 Scala 程序员不用考虑需不需加分号当需要分号的时候Scala 编译器将提醒程序员(通常使用闪烁的错误消息)
此外还有一处微小的改进Scala 不需要包含类定义的文件来反映类的名称一些人将发现这是对 Java 编程的振奋人心的变革那些没有这样做的人可以继续使用 Java 类到文件 的命名约定而不会出现问题
现在看一下 Scala 从何处真正脱离传统的 Java/面向对象代码
将函数和表单最终结合起来
对于初学者Java 发烧友将注意到HelloWorld 是使用关键字 object 来定义的而不是使用 class这是 Scala 对单例模式(Singleton pattern)的认可 — object 关键字告诉 Scala 编译器这将是个单例对象因此 Scala 将确保只有一个 HelloWorld 实例存在基于同样的原因注意 main 没有像在 Java 编程中一样被定义为静态方法事实上Scala 完全避开了 static 的使用如果应用程序需要同时具有某个类型的实例和某种 全局 实例则 Scala 应用程序将允许以相同的名字同时定义 class 和 object
接下来注意 main 的定义与 Java 代码一样是 Scala 程序可接受的输入点它的定义虽然看起来与 Java 的定义不同实际上是等同的main 接受 String 数组作为参数且不返回任何值但是在 Scala 中此定义看起来与 Java 版本稍有差异args 参数被定义为 args Array[String]
在 Scala 中数组表示为泛型化的 Array 类的实例这正是 Scala 使用方括号([])而非尖括号(<>)来指明参数化类型的原因此外为了保持一致性整个语言中都使用 name type 的这种模式
与其他传统函数语言一样Scala 要求函数(在本例中为一个方法)必须始终返回一个值因此它返回称为 unit 的 无值 值针对所有的实际目的Java 开发人员可以将 unit 看作 void至少目前可以这样认为
方法定义的语法似乎比较有趣当它使用 = 操作符时就像将随后的方法体赋值给 main 标识符事实上真正发生的事情是在函数语言中就像变量和常量一样函数是一级概念所以语法上也是一样地处理
您说的是闭包吗?
函数作为一级概念的一个含义是它们必须被识别为单独的结构也称为闭包这是 Java 社区最近一直热烈争论的话题在 Scala 中这很容易完成考虑清单 中的程序此程序定义了一个函数该函数每隔一秒调用一次另一个函数
清单 Timerscala
object Timer
{
def oncePerSecond(): unit =
{
while (true)
{
Systemoutprintln(Time flies when youre having fun(ctionally))
Threadsleep()
}
}
def main(args: Array[String]): unit =
{
oncePerSecond
}
}
不幸的是这个特殊的代码并没有什么功能 …… 或者甚至没任何用处例如如果想要更改显示的消息则必须修改 oncePerSecond 方法的主体传统的 Java 程序员将通过为 oncePerSecond 定义 String 参数来包含要显示的消息但甚至这样也是极端受限的其他任何周期任务(比如 ping 远程服务器)将需要各自版本的 oncePerSecond这很明显违反了 不要重复自己 的规则我认为我可以做得更好
清单 Timerscala
object Timer
{
def oncePerSecond(callback: () => unit): unit =
{
while (true)
{
callback()
Threadsleep()
}
}
def timeFlies(): unit =
{ Consoleprintln(Time flies when youre having fun(ctionally)); }
def main(args: Array[String]): unit =
{
oncePerSecond(timeFlies)
}
}
现在事情开始变得有趣了在清单 中函数 oncePerSecond 接受一个参数但其类型很陌生形式上名为 callback 的参数接受一个函数作为参数只要传入的函数不接受任何参数(以 () 指示)且无返回(由 => 指示)值(由函数值 unit 指示)就可以使用此函数然后请注意在循环体中我使用 callback 来调用传递的参数函数对象
幸运的是我在程序的其他地方已经有了这样一个函数名为 timeFlies所以我从 main 中将其传递给 oncePerSecond 函数(您还会注意到timeFlies 使用了一个 Scala 引入的类 Console它的用途与 Systemout 或新的 javaioConsole 类相同这纯粹是一个审美问题Systemout 或 Console 都可以在这里使用)
匿名函数您的函数是什么?
现在这个 timeFlies 函数似乎有点浪费 — 毕竟它除了传递给 oncePerSecond 函数外毫无用处所以我根本不会正式定义它如清单 所示
清单 Timerscala
object Timer
{
def oncePerSecond(callback: () => unit): unit =
{
while (true)
{
callback()
Threadsleep()
}
}
def main(args: Array[String]): unit =
{
oncePerSecond(() =>
Consoleprintln(Time flies oh you get the idea))
}
}
在清单 中主函数将一块任意代码作为参数传递给 oncePerSecond看起来像来自 Lisp 或 Scheme 的 lambda 表达式事实上这是另一种闭包这个匿名函数 再次展示了将函数当作一级公民处理的强大功能它允许您在继承性以外对代码进行全新地泛化(Strategy 模式的粉丝们可能已经开始唾沫横飞了)
事实上oncePerSecond 仍然太特殊了它具有不切实际的限制即回调将在每秒被调用我可以通过接受第二个参数指明调用传递的函数的频率来将其泛化如清单 所示
清单 Timerscala
object Timer
{
def periodicCall(seconds: int callback: () => unit): unit =
{
while (true)
{
callback()
Threadsleep(seconds * )
}
}
def main(args: Array[String]): unit =
{
periodicCall( () =>
Consoleprintln(Time flies oh you get the idea))
}
}
这是函数语言中的公共主题创建一个只做一件事情的高级抽象函数让它接受一个代码块(匿名函数)作为参数并从这个高级函数中调用这个代码块例如遍历一个对象集合无需在 for 循环内部使用传统的 Java 迭代器对象而是使用一个函数库在集合类上定义一个函数 — 通常叫做 iter 或 map — 接受一个带单个参数(要迭代的对象)的函数例如上述的 Array 类具有一个函数 filter此函数在清单 中定义
清单 Arrayscala 的部分清单
class Array[A]
{
//
def filter (p : (A) => Boolean) : Array[A] = // not shown
}
清单 声明 p 是一个接受由 A 指定的泛型参数的函数然后返回一个布尔值Scala 文档表明 filter 返回一个由满足谓词 p 的数组的所有元素组成的数组这意味着如果我想返回我的 Hello World 程序查找所有以字母 G 开头的命令行参数则可以编写像清单 一样简单的代码
清单 Hello Gmen!
object HelloWorld
{
def main(args: Array[String]): unit = {
argsfilter( (arg:String) => argstartsWith(G) )
foreach( (arg:String) => Consoleprintln(Found + arg) )
}
}
此处filter 接受谓词这是一个隐式返回布尔值(startsWith() 调用的结果)的匿名函数并使用 args 中的每个元素来调用谓词如果谓词返回 true则它将此值添加到结果数组中遍历了整个数组之后它接受结果数组并将其返回然后此数组立即用作 foreach 调用的来源此调用执行的操作就像它名字的含义一样foreach 接受另一个函数并将此函数应用于数组中的每个元素(在本例中仅显示每个元素)
不难想象等同于上述 HelloGscala 的 Java 是什么样的而且也不难发现 Scala 版本非常简短也非常清晰
结束语
Scala 中的编程如此地熟悉同时又如此地不同相似之处在于您可以使用已经了解而且钟爱多年的相同的核心 Java 对象但明显不同的是考虑将程序分解成部分的方式在面向 Java 开发人员的 Scala 指南 的第一篇文章中我仅仅简单介绍了 Scala 的功能将来还有很多内容尚待挖掘但是现在让我们陶醉在函数化的过程中吧!
参考资料
您可以参阅本文在 developerWorks 全球站点上的 英文原文