`
lw9956164
  • 浏览: 26528 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
最近访客 更多访客>>
社区版块
存档分类
最新评论

Java 开发人员的 Scala 指南: 面向对象的函数编程

阅读更多
函数概念

开始之前,我将列出一些必要的函数概念,以帮助理解为何 Scala 以这种方式操作和表现。如果您对函数语言 — Haskell、ML 或函数领域的新成员 F# — 比较熟悉,就请看看scala与java的区别吧。

函数语言的名称源于这样一种概念:程序行为应该像数学函数一样;换句话说,给定一组输入,函数应始终返回相同的输出。这不仅意味着每个函数必须返回一个值,还意味着从一个调用到下一个调用,函数本质上不得具有内蕴状态(intrinsic state)。这种无状态的内蕴概念(在函数/对象领域中,默认情况下指的是永远不变的对象),是函数语言被认为是并发领域伟大的 “救世主” 的主要原因。

与许多最近开始在 Java 平台上占有一席之地的动态语言不同,Scala 是静态类型的,正如 Java 代码一样。但是,与 Java 平台不同,Scala 大量利用了类型推断(type inferencing),这意味着,编译器深入分析代码以确定特定值的类型,无需编程人员干预。类型推断需要较少的冗余类型代码。例如,考虑声明本地变量并为其赋值的 Java 代码,如清单 1 所示:


清单 1. 声明本地变量并为其赋值的 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 I'm assigning a String to it?";
  }
}



Scala 不需要任何这种手动操作,稍后我将介绍。

大量的其他函数功能(比如模式匹配)已经被引入到 Scala 语言中,但是将其全部列出超出了本文的范围。Scala 还添加许多目前 Java 编程中没有的功能,比如操作符重载(它完全不像大多数 Java 开发人员所想象的那样), 具有 “更高和更低类型边界” 的泛型、视图等。与其他功能相比,这些功能使得 Scala 在处理特定任务方面极其强大,比如处理或生成 XML。

但抽象概述并不够:程序员喜欢看代码,所以让我们来看一下 Scala 可以做什么。


--------------------------------------------------------------------------------
回页首
开始认识您

根据计算机科学的惯例,我们的第一个 Scala 程序将是标准的演示程序 “Hello World”:


Listing 2. Hello.Scala
                
object HelloWorld {
  def main(args: Array[String]): unit = {
    System.out.println("Hello, Scala!")
  }
}
 


使用 scalac Hello.scala 编译此程序,然后使用 Scala 启动程序(scala HelloWorld)或使用传统的 Java 启动程序运行生成的代码,注意,将 Scala 核心库包括在 JVM 的类路径(java -classpath %SCALA_HOME%\lib\scala-library.jar;. HelloWorld)中。不管使用哪一种方法,都应出现传统的问候。

清单 2 中的一些元素对于您来说一定很熟悉,但也使用了一些新元素。例如,首先,对 System.out.println 的熟悉的调用演示了 Scala 对底层 Java 平台的忠诚。Scala 充分利用了 Java 平台可用于 Scala 程序的强大功能。(事实上,它甚至会允许 Scala 类型继承 Java 类,反之亦然,但更多信息将在稍后介绍。)

另一方面,如果仔细观察,您还会注意到,在 System.out.println 调用的结尾处缺少分号;这并非输入错误。与 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 中,这很容易完成。考虑清单 3 中的程序,此程序定义了一个函数,该函数每隔一秒调用一次另一个函数:


清单 3. Timer1.scala
                
object Timer
{
  def oncePerSecond(): unit =
  {
    while (true)
    {
      System.out.println("Time flies when you're having fun(ctionally)...")
      Thread.sleep(1000)
    }
  }

  def main(args: Array[String]): unit =
  {
    oncePerSecond
  }
}
 


不幸的是,这个特殊的代码并没有什么功能 …… 或者甚至没任何用处。例如,如果想要更改显示的消息,则必须修改 oncePerSecond 方法的主体。传统的 Java 程序员将通过为 oncePerSecond 定义 String 参数来包含要显示的消息。但甚至这样也是极端受限的:其他任何周期任务(比如 ping 远程服务器)将需要各自版本的 oncePerSecond,这很明显违反了 “不要重复自己” 的规则。我认为我可以做得更好。


清单 4. Timer2.scala
                
object Timer
{
  def oncePerSecond(callback: () => unit): unit =
  {
    while (true)
    {
      callback()
      Thread.sleep(1000)
    }
  }

  def timeFlies(): unit = 
  { Console.println("Time flies when you're having fun(ctionally)..."); }

  def main(args: Array[String]): unit =
  {
    oncePerSecond(timeFlies)
  }
}



现在,事情开始变得有趣了。在清单 4 中,函数 oncePerSecond 接受一个参数,但其类型很陌生。形式上,名为 callback 的参数接受一个函数作为参数。只要传入的函数不接受任何参数(以 () 指示)且无返回(由 => 指示)值(由函数值 unit 指示),就可以使用此函数。然后请注意,在循环体中,我使用 callback 来调用传递的参数函数对象。

幸运的是,我在程序的其他地方已经有了这样一个函数,名为 timeFlies。所以,我从 main 中将其传递给 oncePerSecond 函数。(您还会注意到,timeFlies 使用了一个 Scala 引入的类 Console,它的用途与 System.out 或新的 java.io.Console 类相同。这纯粹是一个审美问题;System.out 或 Console 都可以在这里使用。)


--------------------------------------------------------------------------------
回页首
匿名函数,您的函数是什么?

现在,这个 timeFlies 函数似乎有点浪费 — 毕竟,它除了传递给 oncePerSecond 函数外毫无用处。所以,我根本不会正式定义它,如清单 5 所示:


清单 5. Timer3.scala
                
object Timer
{
  def oncePerSecond(callback: () => unit): unit =
  {
    while (true)
    {
      callback()
      Thread.sleep(1000)
    }
  }

  def main(args: Array[String]): unit =
  {
    oncePerSecond(() => 
      Console.println("Time flies... oh, you get the idea."))
  }
} 


在清单 5 中,主函数将一块任意代码作为参数传递给 oncePerSecond,看起来像来自 Lisp 或 Scheme 的 lambda 表达式,事实上,这是另一种闭包。这个匿名函数 再次展示了将函数当作一级公民处理的强大功能,它允许您在继承性以外对代码进行全新地泛化。(Strategy 模式的粉丝们可能已经开始唾沫横飞了。)

事实上,oncePerSecond 仍然太特殊了:它具有不切实际的限制,即回调将在每秒被调用。我可以通过接受第二个参数指明调用传递的函数的频率,来将其泛化,如清单 6 所示:


清单 6. Timer4.scala
                
object Timer
{
  def periodicCall(seconds: int, callback: () => unit): unit =
  {
    while (true)
    {
      callback()
      Thread.sleep(seconds * 1000)
    }
  }

  def main(args: Array[String]): unit =
  {
    periodicCall(1, () => 
      Console.println("Time flies... oh, you get the idea."))
  }
} 

这是函数语言中的公共主题:创建一个只做一件事情的高级抽象函数,让它接受一个代码块(匿名函数)作为参数,并从这个高级函数中调用这个代码块。例如,遍历一个对象集合。无需在 for 循环内部使用传统的 Java 迭代器对象,而是使用一个函数库在集合类上定义一个函数 — 通常叫做 “iter” 或 “map” — 接受一个带单个参数(要迭代的对象)的函数。例如,上述的 Array 类具有一个函数 filter,此函数在清单 7 中定义:


清单 7. Array.scala 的部分清单
                
class Array[A]
{
    // ...
  	def filter  (p : (A) => Boolean) : Array[A] = ... // not shown
}
 


清单 7 声明 p 是一个接受由 A 指定的泛型参数的函数,然后返回一个布尔值。Scala 文档表明 filter “返回一个由满足谓词 p 的数组的所有元素组成的数组”。这意味着如果我想返回我的 Hello World 程序,查找所有以字母 G 开头的命令行参数,则可以编写像清单 8 一样简单的代码:


清单 8. Hello, G-men!
                
object HelloWorld
{
  def main(args: Array[String]): unit = {
    args.filter( (arg:String) => arg.startsWith("G") )
        .foreach( (arg:String) => Console.println("Found " + arg) )
  }
}



此处,filter 接受谓词,这是一个隐式返回布尔值(startsWith() 调用的结果)的匿名函数,并使用 args 中的每个元素来调用谓词。如果谓词返回 true,则它将此值添加到结果数组中。遍历了整个数组之后,它接受结果数组并将其返回,然后此数组立即用作 “foreach” 调用的来源,此调用执行的操作就像它名字的含义一样:foreach 接受另一个函数,并将此函数应用于数组中的每个元素(在本例中,仅显示每个元素)。

不难想象等同于上述 HelloG.scala 的 Java 是什么样的,而且也不难发现 Scala 版本非常简短,也非常清晰。

结束语

Scala 中的编程如此地熟悉,同时又如此地不同。相似之处在于您可以使用已经了解而且钟爱多年的相同的核心 Java 对象,但明显不同的是考虑将程序分解成部分的方式。在面向 Java 开发人员的 Scala 指南 的第一篇文章中,我仅仅简单介绍了 Scala 的功能。将来还有很多内容尚待挖掘,但是现在让我们陶醉在函数化的过程中吧!
分享到:
评论

相关推荐

    面向 Java 开发人员的 Scala 指南

    面向 Java 开发人员的 Scala 指南 Scala是一个结合面向对象与函数编程的新兴语言。

    Scala实用指南

    要读者熟悉 Scala 编程语言,但需要读者具备 Java、面向对象编程的背景知识。因为本书以 一种非常务实的方式组织内容,所以读者无法学到 Scala 的所有内容,但是足以应付日常工 作,如果想要更全面地学习 Scala 以及...

    Scala实用指南-高清带目录

    感谢你选择本书来学习和练习这门编程语言,你将感受到在一种编程语言中融合面向对象和函数式编程这两种编程范式所带来的巨大优势。 Java 生态系统是目前用于开发和部署企业级应用最强大的平台之一。Java 平台几乎...

    Scala快速入门

    Cay S.Horstmann所著的《快学Scala》内容简介:Scala是一门以Java 虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用...

    scala-2.13.1.tgz

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    快学 Scala.pdf

    Cay S.Horstmann所著的《快学Scala》内容简介:Scala是一门以Java 虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的**特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发...

    快学Scala 电子书 pdf

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    快学Scala.中文完整版

    《快学Scala》是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它...

    Scala程序设计(第2版)

    第8章 Scala面向对象编程 210 8.1 类与对象初步 211 8.2 引用与值类型 213 8.3 价值类 214 8.4 父类 217 8.5 Scala的构造器 217 8.6 类的字段 221 8.6.1 统一访问原则 223 8.6.2 一元方法...

    快学scala第二版中英文.zip

    Scala是一门主要以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的*佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala默认运行于JVM之上,因此...

    快学Scala PDF扫描版

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    Scala入门必看

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    《快学Scala》PDF中文版

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    快学Scala(中文完整版)

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    快学scala中文

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    快学Scala 中文版

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    java源码嵌套for循环-allaboutscala:www.allaboutscala.com教程的源代码

    循环的java源码压缩Scala ...章:认为您了解使用类的面向对象吗? 第 5 章:使用 trait 重新发明依赖注入 第 6 章:不可变集合 第 7 章:可变集合 第 8 章:集合函数 第 9 章:期货 第 10 章:备忘单和 Scala 测

    快学Scala 中文版带目录

    《快学Scala》是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它...

Global site tag (gtag.js) - Google Analytics