《Spark大数据分析:核心概念、技术及实践》Scala编程

    xiaoxiao2024-03-29  8

    本节书摘来自华章出版社《Spark大数据分析:核心概念、技术及实践》一书中的第1章,第2节,作者穆罕默德·古勒(Mohammed Guller)更多章节内容可以访问云栖社区“华章计算机”公众号查看。

    Scala编程

    Scala是当前热门的现代编程语言之一。它是编程语言界的凯迪拉克。它是一门强大且优美的语言。学会了它,对你的职业生涯大有裨益。

    用不同的编程语言都可以编写大数据应用程序,比如Java、Python、C++、Scala等。Hadoop本身就是用Java编写的。尽管大多数的Hadoop应用程序都是用Java编写的,但它也支持用其他语言来编写。类似地,Spark是由Scala编写的,但是它也支持其他的语言,包括Scala、Java、Python和R。

    对于开发大数据应用程序而言,Scala是一门很棒的语言。使用它有诸多好处。首先,开发者使用Scala能显著提高生产力。其次,它能帮助开发者减少bug,写出健壮的代码。最后,Spark就是用Scala编写的,因而对于开发Spark应用而言使用Scala是最自然的。

    本章把Scala当作一门通用语言来进行介绍。本章的目的并不是让你成为Scala专家,而是让你有足够多的Scala知识从而能理解并使用Scala来编写Spark应用。本书的示例代码全部都由Scala编写,所以掌握Scala,将更容易理解本书的内容。如果你已经学会了Scala,那么你可以跳过这一章。

    基于上述目的,本章只介绍Scala编程的基础知识。为了能更高效地使用Scala,了解函数式编程是很重要的,所以本章将首先介绍函数式编程。最后,将介绍如何编写一个单独的Scala应用程序。

    2.1 函数式编程

    函数式编程是一种编程范式,它把函数作为代码的基本构成元素,避免使用命令式编程中的可变变量以及循环等控制结构。它把计算当作对数学函数的求值,函数的输出完全依赖于传递给函数的参数值。程序就是由这些函数构成的。总之,在函数式编程语言中函数是一等公民。

    在这几年,函数式编程引起了大量的关注。甚至一些主流语言(比如C++、Java和Python)都添加了对函数式编程的支持。它的流行主要有如下几个原因。

    首先,使用函数式编程能极大地提升生产效率。相比于命令式编程,在解决同样的问题上,函数式编程只需要更少的代码。举例来说,在Java中一个用一百行代码实现的功能,用Scala来实现只需要10行或20行即可。函数式编程能带来5~10倍的生产效率提升。

    其次,函数式编程更容易编写多并发或多线程的应用。随着多核、多CPU计算机的来临,编写多进程应用显得愈发重要。在当前提升单CPU晶体管数量已经越来越难的情况下,硬件产商开始采用增加CPU数量、增加内核数的方式来维持摩尔定律的有效性。现在多核计算机已经很普遍了。应用程序也需要利用多核带来的优势。相对于命令式编程语言,使用函数式编程语言更易于编写利用多核的应用程序。

    再者,函数式编程能帮助你写出健壮的代码。它可以帮你避免一些常见的编程错误。而且一般来说,应用中bug的数量与代码和行数成正比。由于函数式编程相对于命令式编程通常代码行数更少,因此使用它将带来更少的bug。

    最后,使用函数式编程语言更容易写出便于阅读、理解的优雅代码。如果运用得当,用函数式编程语言写出来的代码看上去是很漂亮的,既不复杂也不混乱。你将从你的代码中得到巨大的快乐和满足。

    本节将会介绍函数式编程中的几个重要概念。

    2.1.1 函数

    函数是一段可以执行的代码。通过函数,程序员可以将一个大型的程序分割成一些方便管理的代码片段。在函数式编程中,应用程序就是构建在函数之上的。

    尽管很多编程语言都支持函数的感念,但是函数式编程语言把函数当成一等公民。而且,函数是可以随意组合的,并没有其他的副作用。

    一等公民

    函数式编程把函数当成一等公民。函数拥有和变量、值一样的地位。函数可以像变量一样使用。如果你把函数式编程中的函数和命令式编程语言(比如C)中的函数对比,更容易理解这个概念。

    命令式编程语言中变量和函数是区别对待的。举例来说,C语言中是不允许在函数内部定义函数的,它也不允许把一个函数当成参数传给另外一个函数。

    函数式编程允许把函数当成参数传递给另一个函数。函数也可以当成返回值从另一个函数中返回。函数可以在任何地方定义,包括在其他函数内部。它可以写成匿名函数字面量,然后像字符串字面量一样作为参数传递给另一个函数。

    可组合

    函数式编程中,函数是可以随意组合的。函数组合指的是可以把一些简单的函数组合在一起从而创建一个复杂的函数,它是一个数学概念也是一个计算机科学概念。比如,两个可组合的函数可以组合成一个新函数。考虑下面两个数学函数。

    f(x)=x*2

    g(x)=x+2

    函数f以一个数字作为输入,将它的两倍作为输出。函数g以一个数字作为输入,将它的值加2作为输出。

    如下所示,可以将f和g组合为一个新的函数。

    h(x)=f(g(x))=f(x+2)=(x+2)*2

    对于同样的输入,函数h的效果和先调用函数g处理输入,然后将函数g的输出作为参数调用函数f是一样的。

    为了解决复杂的问题,可以将它分解成几个小问题,函数组合在这种方式下是很有用的。对于每个小问题可以写若干个函数,最后再把它们组合起来,从而解决这个复杂的问题。

    无副作用

    在函数式编程中函数并没有什么副作用。函数的返回值完全依赖于传递给它的参数。函数的行为并不会随着时间的改变而改变。对于给定的参数值,无论调用这个函数多少次,始终返回同样的结果。换句话说,函数是无状态的,它既不依赖也不会改变任何全局变量的值。

    函数的无副作用性有如下几个好处。首先,它们可以按任意顺序组合在一起。其次,便于理解代码。最后,使用这样的函数便于编写多线程的应用程序。

    简单

    函数式编程中的函数是很简单的。一个函数由几行代码构成,并且只做一件事。一个简单的函数是便于理解的。函数也保证了代码的健壮性以及高质量。

    可组合的简单函数很适合用来实现复杂的算法,并且不容易出错。函数式编程鼓励人们不断把问题分解成若干个子问题,直到一个子问题能够用一个简单函数去解决为止。然后,把这些简单函数组合起来就能得到解决这个复杂问题的函数了。

    2.1.2 不可变数据结构

    函数式编程强调使用不可变数据结构。纯函数方式程序并不会使用任何可变数据结构或变量。换句话说,数据从不会在原地修改,这与命令式编程语言(比如C/C++、Java和Python)相比是不同的。没有函数式编程背景的人很难想象没有可变变量的程序是什么样子的。实际上,使用不可变数据结构写代码并非难事。

    使用不可变数据结构具有如下好处。首先,它能减少bug。使用不可变数据结构编写的代码便于理解。另外,函数式编程语言的编译器会强制使用不可变的数据结构。因而,很多bug在编译期间就能捕获。

    其次,使用不可变数据结构便于编写多线程应用程序。编写一个能充分利用多核的应用程序并不容易。竞争条件和数据损坏在多线程应用程序中是常见的问题。使用不可变数据结构有助于避免这些问题。

    2.1.3 一切皆表达式

    在函数式编程中,每一个语句都是一个表达式,都会有返回值。比如Scala中的if-else控制结构就是一个有返回值的表达式。这与可以在if-else中写多个语句的命令式编程语言显著不同。

    这一特性有助于不用可变变量编写应用程序。

    2.2 Scala基础

    Scala是一门支持面向对象编程和函数式编程的混合语言。它支持函数式编程的概念,比如不可变数据结构,把函数视为一等公民。在面向对象方面,它也支持类、对象、特质、封装、继承、多态和其他面向对象的概念。

    Scala是一门静态类型语言。Scala应用程序是由Scala编译器编译的。Scala是类型安全的,Scala编译器在编译期间会强制检查类型安全性。这有助于减少应用程序中的bug。

    最后,Scala是一门基于JVM的语言。Scala编译器会将Scala应用程序编译成Java字节码,Java字节码运行在JVM上。从字节码的层面来看,Scala应用程序和Java应用程序没有区别。

    因为Scala是基于JVM的,所以它能和Java无缝互操作。在Java应用程序中可以使用Scala开发的库。更重要的是,Scala应用程序可以使用任何Java库,而无须任何包装器或胶水代码。Scala应用程序可以直接受益于近二十年来人们用Java开发出来的各种现有的库。

    尽管Scala是一门混合了面向对象编程和函数式编程的语言,但是它还是侧重于函数式编程。这一点使它成为一门强大的语言。相比于把Scala当成面向对象语言使用而言,把Scala当成函数式编程语言来用更能让你受益匪浅。

    本书无法覆盖Scala的方方面面。想要说明白Scala的每个细节需要一本更厚的书才行。本书只介绍编写Spark应用程序所需要的基础知识。假设你有一定的编程经验,故本书不会介绍编程的基础知识。

    Scala是一门强大的语言。伴随着强大性而来的是它的复杂性。有些人想要立刻学会Scala的所有语言特性,因而被吓坏了。然而,你并不需要知道它的每个细节,就能高效地使用它。只要你学会了本章所涵盖的基础知识,你就能开始高效地开发Scala应用程序了。

    2.2.1 起步

    学习一门语言的最好方式就是使用它。如果你能运行一下附带的示例代码,你将更好地理解本章的内容。

    可以用任何的文本编辑器编写Scala代码,用scalac编译它,用Scala执行。或者也可以使用由Typesafe提供的基于浏览器的IDE。当然,也可以使用基于Eclipse的Scala IDE、Intellij IDEA或NetBeans IDE。可以从www.scala-lang.org/download下载Scala二进制文件、Typesafe Activator和上述各种IDE。

    学习Scala最快捷的途径就是使用Scala解释器,它提供了一个交互式的shell用于编写代码。它是一个REPL(读取、求值、输出、循环)工具。当你在Scala shell中输入一个表达式时,它会对其求值,将结果输出到控制台上,然后等待你输入下一个表达式。安装交互式Scala shell就像下载Scala二进制文件然后解压它一样简单。Scala shell的名字是scala。它位于bin目录下,只须在终端输入Scala就能启动它。

     

    现在,你应当看到如图2-1所示的Scala shell提示符。

     

    图2-1 Scala shell提示符

    现在,可以输入任何Scala表达式。下面是一个例子。

     

    在你按下回车键之后,Scala解释器就会执行代码,然后将结果输出在控制台上。可以用Scala shell执行本章的示例代码。

    让我们开始学习Scala吧。

    2.2.2 基础类型

    和其他的编程语言类似,Scala提供一些基础类型和作用在它们之上的操作。Scala中的基础类型清单如表2-1所示。

    表2-1 Scala中的基础变量类型

    变 量 类 型 描  述    变 量 类 型 描  述

    Byte    8位有符号整数   Double  32位双精度浮点数

    Short   16位有符号整数  Char    16位无符号

    Int 32位有符号整数  Unicode 字符

    Long    64位有符号整数  String  字符串

    Float   32位单精度浮点数    Boolean true或false

     

    需要注意的是,Scala并没有基本类型。Scala的每一个基础类型都是一个类。当把一个Scala应用程序编译成Java字节码的时候,编译器会自动把Scala基础类型转变成Java基本类型,这样有助于提高应用程序的性能。

    2.2.3 变量

    Scala有两种类型的变量:可变和不可变的。不过,尽量不要使用可变变量。纯函数式程序是不会使用可变变量的。然而,有时使用可变变量会使代码更简单。因此,Scala也支持可变变量。当然,使用它的时候要小心。

    可变变量使用关键字var声明,不可变变量使用关键字val声明。

    val与命令式编程语言(如C/C++和Java)中的变量类似。它可以在创建之后重新赋值。创建、修改可变变量的语法如下。

     

    val在初始化之后就不可以重新赋值了。创建val的语法如下。

     

    上面的代码在添加了下面这行语句之后会发生什么呢?

     

    编译器会报错。

    需要着重指出的是,Scala编译器提供了种种便利。首先,以分号作为语句结尾是可选的。其次,有必要的话,编译器会进行类型推断。Scala是一门静态类型的语言,所以一切都是有类型的。然而,在Scala编译器能自动推导出类型的情况下,开发者不需要为它声明类型。使用Scala编程会使得代码更简短精炼。

    下面两个语句是等价的。

     

    2.2.4 函数

    如前所述,函数是一段可执行的代码,这段代码最终返回一个值。它和数学中函数的概念类似,读取输入,然后返回一个输出。

    Scala把函数当成一等公民。函数可以当成变量使用。它可以作为输入传递给其他函数。它也可以定义成匿名函数字面量,就像字符串字面量一样。它可以作为变量的值。它也可以在其他函数的函数体内定义。它还可以作为其他函数的返回值返回。

    Scala中用关键字def来定义函数。函数定义以函数名开头,紧跟着是以逗号作为分隔符的输入参数列表,每个参数后面跟着它们各自的类型,参数列表放在一对圆括号中。在右圆括号后面是一个冒号、函数返回值类型、等号和函数体。函数体可以被大括号包裹,没有亦可。下面是一个例子。

     

    在上面的例子中,函数名是add。它有两个都是Int类型的参数,返回一个Int类型的返回值。这个函数只是将两个参数相加,而后将累加和作为返回值返回。

    这个函数可以简化成下面这样。

     

    这个简化版的函数和之前的是一样的。返回值的类型省略了,因为编译器可以根据代码推导出来。然而,还是不推荐省略返回值类型。

    简化版中大括号同样也省略了。只有当函数体中有不止一条语句时,才需要大括号。

    关键字return也省略了,因为它是可选的。在Scala中,所有语句都是表达式,表达式总是会返回一个值。因此,简化版函数的返回值就是函数体中最后一个语句作为表达式所返回的值。

    上面的代码片段只是一个例子,说明了使用Scala可以写出简洁的代码,提高代码的可读性和可维护性。

    Scala支持多种类型的函数,这一点在下面进行介绍。

    方法

    方法是指类的成员函数。它的定义和使用方法和函数类似,唯一的区别是它可以访问类里面所有的成员。

    局部函数

    在其他函数中或在方法中定义的函数称为局部函数。它仅可使用输入参数和包含它的函数内的变量。它只在包含其定义的函数内可见。这一实用的特性使得你可以在函数内聚合多条语句,而不会污染整个应用程序的命名空间。

    高阶方法

    把函数作为输入参数的方法被称为高阶方法。类似的,高阶函数指的是把函数作为参数的函数。高阶方法和高阶函数有助于减少重复代码,从而使得代码更简洁。

    下面是一个高阶函数的例子。

     

    encode函数接受两个参数,返回一个Long类型的值。第一个参数是Int类型的。第二个参数是一个函数f,它接受一个Int类型的参数,返回一个Long类型的值。encode函数首先将第一个参数乘以10,然后将结果作为参数调用函数f。

    在介绍Scala集合的时候我们会看到更多的高阶函数。

    函数字面量

    函数字面量是指源代码中的匿名函数。在应用程序中,可以像字符串字面量一样使用它。它可以作为高阶方法或高阶函数的参数,也可以赋值给变量。

    函数字面量的定义由处于圆括号中的输入函数列表、右箭头和函数体构成。包裹函数体的大括号是可选的。下面是一个函数字面量的例子。

     

    如果函数体只由一条语句构成,那么大括号是可以省略的。上面定义的函数字面量的简化版如下所示。

     

    之前定义的高阶函数encode可以被当成函数字面量使用,如下所示。

     

    闭包

    在函数对象的函数体中,只能使用参数和函数字面量中定义的局部变量。然而,Scala中函数字面量却可以使用其所处作用域中的变量。闭包就是这种可以使用了非参数非局部变量的函数字面量。有时候人们把闭包和函数字面量当成同一术语,但是从技术上说,它们是不一样的。

    下面是一个闭包的例子。

     

    在上面的代码中,局部函数encode的第二个参数是个函数。这个函数字面量使用了两个变量n和seed。n是函数的参数,而seed却不是。在这个作为函数encode参数的函数字面量中,seed是从其所处的作用域获得的,并用在函数体中。

    2.2.5 类

    类是面向对象编程中的概念。它是一种高层的编程抽象。简单地说,它是一种将数据和操作结合在一起的代码组织方式。在概念上,它用属性和行为来表示一个实体。

    Scala中的类和其他面向对象编程语言中的类似。它由字段和方法构成。字段就是一个变量,用于存储数据。方法就是一段可执行的代码,是在类中定义的函数。方法可以访问类中的所有字段。

    类就是一个在运行期间创建对象的模板。对象就是一个类实例。类在源代码中定义,而对象只存在于运行期间。Scala使用关键字class来定义一个类。类的定义以类名开头,紧跟着参数列表,参数列表以逗号作为分隔符,然后是处于大括号中的字段和方法。

    下面是一个例子。

     

     

    类实例使用关键字new创建。

     

    类通常用作可变数据结构。对象都有一个随时变化的状态。因此,类中的字段一般都是可变变量。

    因为Scala运行在JVM之上,所以你不必显式删除对象。Java的垃圾回收器会自动删除那些不再使用的对象。

    2.2.6 单例

    在面向对象编程中一个常见的设计模式就是单例,它是指那些只可以实例化一次的类。Scala使用关键字object来定义单例对象。

     

     

    2.2.7 样本类

    样本类是指使用case修饰符的类,下面是一个例子。

     

    对于样本类,Scala提供了一些语法上的便利。首先,样本类会添加与类名一致的工厂方法。因此,不必使用new关键字就可以创建一个样本类的类实例。举例来说,下面的这段代码就是合法的。

     

    其次,样本类参数列表中的所有参数隐式获得val前缀。换句话说,Scala把上面定义的样本类Message当成如下定义。

     

    val前缀把类参数转变成了不可变的类字段。故可以从外部访问它们。

    最后,Scala为样本类添加了方法toString、hashCode、equals、copy。这些方法使得样本类便于使用。

    在创建不可变对象时样本类相当有用,而且样本类还支持模式匹配。模式匹配将在下面进行介绍。

    2.2.8 模式匹配

    模式匹配是Scala中的概念,它看上去类似于其他语言的switch语句。然而,它却是一个比switch语句要强大得多的工具。它就像瑞士军刀一样能解决各种各样的问题。

    模式匹配的一个简单用法就是替代多层的if-else语句。如果代码中有多于两个分支的if-else语句,它就难以阅读了。在这种场景下,使用模式匹配能提高代码的可读性。

    作为一个例子,考虑这样一个简单的函数,它以表示颜色的字符串作为参数,如果是红色返回1,如果是蓝色返回2,如果是绿色返回3,如果是黄色返回4,如果是其他颜色返回0。

     

    Scala使用关键字match来替代关键字switch。每一个可能的选项前面都跟着关键字case。如果有一个选项匹配,那么该选项右箭头右边的代码将会执行。下划线表示默认选项。如果任何选项都不匹配,那么默认选项对应的代码就会执行。

    上面的例子虽然简单,但是它说明模式匹配的几个特性。首先,一旦有一个选项匹配,那么该选项对应的代码就会执行。不同于switch语句,每一个选项对应的代码中不需要有break语句。匹配选项之后的其他选项对应代码并不会被执行。

    其次,每一个选项对应的代码都是表达式,表达式返回一个值。因此,模式匹配语句本身就是一个返回一个值的表达式。下面的代码说明了这一点。

     

    2.2.9 操作符

    Scala为基础类型提供了丰富的操作符。然而,Scala没有内置操作符。在Scala中,每一个基础类型都是一个类,每一个操作符都是一个方法。使用操作符等价于调用方法。考虑下面的例子。

     

    +并不是Scala的内置操作符。它是定义在Int类中的一个方法。上面代码中的最后一条语句等价于如下代码。

     

    Scala允许以操作符的方式来调用方法。

    2.2.10 特质

    特质是类继承关系中的接口。它的这种抽象机制有助于开发者写出模块化、可复用、可扩展的代码。

    从概念上说,一个接口可以定义多个方法。Java中的接口只有函数签名,没有实现。继承这个接口的类必须实现这些接口方法。

    Scala的特质类似于Java中的接口。然后,不同于Java中的接口,Scala特质可以有方法的实现。而且它还可以有字段。这样,继承类就可以复用这些字段和特质中实现的方法了。

    特质看上去像是抽象类,它们都有字段和方法。区别在于一个类只能继承一个抽象类,但是可以继承多个特质。

    下面是一个特质的例子。

     

    2.2.11 元组

    元组是一个容器,用于存放两个或多个不同类型的元素。它是不可变的。它自从创建之后就不能修改了。它的语法简单,如下所示。

     

    当你想要把一些不相关的元素聚合在一起时,元组就派上用场了。当所有元素都是同一类型时,可以使用集合,比如数组或列表。当元素是不同类型但是之间有联系时,可以使用类,把它们当成类字段来存储。但是在某些场景下使用类没有必要。比如你想要有一个有多个返回值的函数,此时元组比类更合适。

    元组的下标从1开始。下面这个例子展示怎么访问元组中的元素。

     

    2.2.12 Option类型

    Option是一种数据类型,用来表示值是可选的,即要么无值要么有值。它要么是样本类Some的实例,要么是单例对象None的实例。Some类的实例可以存储任何类型的数据,用来表示有值。None对象表示无值。

    Option类型可以在函数或方法中作为值返回。返回Some(x)表示有值,x是真正的返回值。返回None表示无值。从函数返回的Option类型对象可以用于模式匹配中。

    下面的例子说明了这些用法。

     

     

    使用Option类型有助于避免空指针异常。在很多语言中,null用于表示无值。以C/C++/Java中一个返回整数的函数为例,如果对于给定的参数没有合法的整数可以返回,函数可能返回null。调用者如果没有检查返回值是否为null,而直接使用,就可以能导致程序崩溃。在Scala中,由于有了严格类型检查和Option类型,这样的错误得以避免。

    2.2.13 集合

    集合是一种容器类的数据结构,可以容纳零个或多个元素。它是一种抽象的数据结构。它支持声明式编程。它有方便使用的接口,使用这些接口就不必手动遍历所有元素了。

    Scala有丰富的集合类,集合类包含各种类型。所有的集合类都有同样的接口。因此只要你熟悉了其中的一种集合,对于其他的集合类型你也能熟练使用。

    Scala的集合类可以分为三类:序列、集合、map。本节将介绍Scala最常用的集合类。

    序列

    序列表示有先后次序的元素集合。由于元素是有次序的,因此可以根据位置来访问集合中的元素。举例来说,可以访问序列中第n个元素。

    数组

    数组是一个有索引的元素序列。数组中的所有元素都是相同类型的。它是可变的数据结构。可以修改数组中的元素,但是你不能在它创建之后增加元素。它是定长的。

    Scala数组类似其他语言中的数组。访问其中的任意一个元素都占用固定的时间。数组的索引从0开始。要访问元素或修改元素,可以通过索引值达成,索引值位于括号内。下面是一个例子。

     

    关于数组的基本操作如下:

    通过索引值访问元素

    通过索引值修改元素

    列表

    列表是一个线性的元素序列,其中存放一堆相同类型的元素。它是一种递归的结构,而不像数组(扁平的数据结构)。和数组不同,它是不可变的,创建后即不可修改。列表是Scala和其他函数式语言中最常使用的一种数据结构。

    尽管可以根据索引来访问列表中的元素,但这并不高效。访问时间和元素在列表中的的位置成正比。

    下面的代码展示了几种创建列表的方式。

     

    关于列表的基本操作如下:

    访问第一个元素。为此,List类提供了一个叫作head的方法。

    访问第一个元素之后的所有元素。为此,List类提供了一个叫作tail的方法。

    判断列表是否为空。为此,List类提供了一个叫作isEmpty的方法,当列表为空时,它返回true。

    向量

    向量是一个结合了列表和数组各自特性的类。它拥有数组和列表各自的性能优点。根据索引访问元素占用固定的时间,线性访问元素也占用固定的时间。向量支持快速修改和访问任意位置的元素。

    下面是一个例子。

     

    集合

    集合是一个无序的集合,其中的每一个元素都不同。它没有重复的元素,而且,也没法通过索引来访问元素,因为它没有索引。

    下面是一个例子。

     

    集合支持两种基本操作。

    contains:如果当前集合包含这个元素,则返回true。元素作为参数传递进来。

    isEmpty:如果当前集合为空,则返回true。

    map

    map是一个键-值对集合。在其他语言中,它也叫作字典、关联数组或hash map。它是一个高效的数据结构,适合根据键找对应的值。千万不要把它和Hadoop MapReduce中的map混淆了。Hadoop MapReduce中的map指在集合上的一种操作。

    下面这段代码展示了如何创建并使用map。

     

    Scala还有其他集合类,这里就不一一介绍了。然而,只要掌握了以上内容,这些就足以高效地使用Scala了。

    集合类上的高阶方法

    Scala集合的强大之处就在于这些高阶方法。这些高阶方法把函数当成参数。需要注意的是,这些高阶方法并没有改变集合。

    本节将介绍一些常用的高阶方法。例子中使用的是List集合,但是所有的Scala集合类都支持这些高阶方法。

    map

    map方法的参数是一个函数,它将这个函数作用于集合中的每一个元素,返回由其返回值所组成的集合。这个返回的集合中的元素个数和调用map的集合元素个数一致。然而,返回集合中的元素类型有可能不一样。

    下面是一个例子。

     

    在上面的例子中,需要注意的是,xs的类型是List[Int],而ys的类型是List[Double]。

    如果一个函数只有一个参数,那么包裹参数列表的圆括号可以用大括号代替。下面的两条语句是等价的。

     

    就像之前说的一样,Scala允许以操作符的方式调用任何方法。为了进一步提高了代码的可读性。上面的代码可以改写成如下这样。

     

    Scala会根据集合中元素类型对函数字面量中的参数进行类型推导,故可以省略参数类型。下面的两条语句是等价的。

     

    如果函数字面量的参数只在函数体内使用一次,那么右箭头及其左边部分都可以省略。可以只写函数字面量的主体。下面的两条语句是等价的。

     

    下划线表示集合中的元素,它作为参数传递给map中的函数字面量。上面的代码可以解读为将xs中的每个元素乘以10。

    总之,下面分别是详细版和简化版的代码。

     

    如你所见,使用Scala能很方便地写出易读的简洁代码。

    flatMap

    Scala集合的flatMap方法类似于map,它的参数是一个函数,它把这个函数作用于集合中的每一个元素,返回另外一个集合。这个函数作用于原集合中的一个元素之后会返回一个集合。这样,最后就会得到一个元素都是集合的集合。使用map方法,就是这样的结果。但是使用flatMap会得到一个扁平化的集合。

    下面是一个使用flatMap的例子。

     

    toList方法将创建一个列表,里面包含原有集合的所有元素。这个方法能将字符串、数组、集合或其他集合类型转变成一个列表。

    filter

    filter方法将谓词函数作用于集合中的每个元素,返回另一个集合,其中只包含计算结果为真的元素。谓词函数指的是返回一个布尔值的函数。它要么返回true,要么返回false。

     

    foreach

    foreach方法的参数是一个函数,它把这个函数作用于集合中的每一个元素,但是不返回任何东西。它和map类似,唯一的区别在于map返回一个集合,而foreach不返回任何东西。由于它的无返回值特性它很少使用。

     

    reduce

    reduce方法返回一个值。顾名思义,它将一个集合整合成一个值。它的参数是一个函数,这个函数有两个参数,并返回一个值。从本质上说,这个函数是一个二元操作符,并且满足结合律和交换律。

    下面是一些例子。

     

    下面是一个找出句子中最长单词的例子。

     

    需要注意的是,Hadoop MapReduce中的map/reduce和我们上面说的map/reduce是相似的。事实上,Haddoop MapReduce借用了函数式编程中的这些概念。

    2.3 一个单独的Scala应用程序

    到目前为止,你看到不少Scala代码片段。在这一节,我们将会编写一个完整的Scala应用程序,你可以编译它,运行它。

    一个单独的Scala应用程序需要一个具有main方法的单例对象。这个main方法以一个Array[String]类型的参数作为输入。它并不返回任何值。它是这个Scala应用程序的入口。这个有main方法的单例可以随意起名。

    下面展示的是一个输出Hello World!的Scala应用程序。

     

    你可以把上面的代码写到文件中,编译并运行它。Scala源代码文件以.Scala作为后缀名。但这不是必需的。不过建议以代码中的类名或单例名作为文件名。比如,上面的代码文件应该叫作HelloWorld.scala。

    2.4 总结

    Scala是一门运行在JVM之上的静态类型语言,它用来开发多线程和分布式的应用程序。它结合了面向对象编程和函数式编程各自的优点。而且,它可以和Java无缝集成在一起。可以在Scala中使用Java的库,反之亦然。

    使用Scala不仅能让开发者显著提高生产力和代码质量,还可以开发出健壮的多线程和分布式应用程序。

    Spark本身是用Scala编写的。它只是众多使用Scala编写的流行的分布式系统中的一个代表。

     

    最新回复(0)