1 Scala语言概述

1.1 计算机的缘起

•数学家阿隆佐•邱奇(Alonzo Church)设计了“λ演算”,这是一套用于研究函数定义、函数应用和递归的形式系统

•λ演算被视为最小的通用程序设计语言

•λ演算的通用性就体现在,任何一个可计算函数都能用这种形式来表达和求值

•λ演算是一个数理逻辑形式系统,强调的是变换规则的运用,而非实现它们的具体机器

•英国数学家阿兰·图灵采用了完全不同的设计思路,提出了一种全新的抽象计算模型——图灵机

•图灵机是现代计算机的鼻祖。现有理论已经证明,λ演算和图灵机的计算能力是等价的

•冯·诺依曼(John Von Neumann)将图灵的理论物化成为实际的物理实体,成为了计算机体系结构的奠基者

•1945年6月,冯·诺依曼提出了在数字计算机内部的存储器中存放程序的概念,这是所有现代计算机的范式,被称为“冯·诺依曼结构”

1.2 编程范式

•编程范式是指计算机编程的基本风格或典范模式。常见的编程范式主要包括命令式编程和函数式编程。面向对象编程就属于命令式编程,比如C++、Java等

•命令式语言是植根于冯·诺依曼体系的,一个命令式程序就是一个冯·诺依曼机的指令序列,给机器提供一条又一条的命令序列让其原封不动地执行

•函数式编程,又称泛函编程,它将计算机的计算视为数学上的函数计算

•函数编程语言最重要的基础是λ演算。典型的函数式语言包括Haskell、Erlang和Lisp等

•一个很自然的问题是,既然已经有了命令式编程,为什么还需要函数式编程呢?

•为什么在C++、Java等命令式编程流行了很多年以后,近些年函数式编程会迅速升温呢?

•命令式编程涉及多线程之间的状态共享,需要锁机制实现并发控制

•函数式编程不会在多个线程之间共享状态,不需要用锁机制,可以更好并行处理,充分利用多核CPU并行处理能力

1.3 Scala简介

Scala是一门类Java的多范式语言,它整合了面向对象编程和函数式编程的最佳特性。

•Scala运行于Java虚拟机(JVM)之上,并且兼容现有的Java程序

•Scala是一门纯粹的面向对象的语言

•Scala也是一门函数式语言

Scala是一门类Java的多范式语言,它整合了面向对象编程和函数式编程的最佳特性。具体来讲:

•Scala运行于Java虚拟机(JVM)之上,并且兼容现有的Java程序,可以与Java类进行互操作,包括调用Java方法,创建Java对象,继承Java类和实现Java接口

•Scala是一门纯粹的面向对象的语言。在Scala语言中,每个值都是对象,每个操作都是方法调用。对象的数据类型以及行为由类和特质描述。类抽象机制的扩展有两种途径,一种途径是子类继承,另一种途径是灵活的混入(mixin)机制,这两种途径能避免多重继承的种种问题

•Scala也是一门函数式语言。在Scala语言中,每个函数都是一个值,并且和其他类型(如整数、字符串等)的值处于同一地位。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化

2 Scala基础

2.1基本数据类型和变量

1.基本数据类型

•Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double和Boolean(注意首字母大写)

•和Java不同的是,在Scala中,这些类型都是“类”,并且都是包scala的成员,比如,Int的全名是scala.Int。对于字符串,Scala用java.lang.String类来表示字符串

字面量(literal)

  1. val i = 123 //123就是整数字面量
  2. val i = 3.14 //3.14就是浮点数字面量
  3. val i = true //true就是布尔型字面量
  4. val i = 'A' //'A'就是字符字面量
  5. val i = Hello //“Hello”就是字符串字面量

2.基本操作

操作符

•算术运算符:加(+)、减(-) 、乘(*) 、除(/) 、余数(%);

•关系运算符:大于(>)、小于(<)、等于(==)、不等于(!=)、大于等于(>=)、小于等于(<=)

•逻辑运算符:逻辑与(&&)、逻辑或(||)、逻辑非(!);

•位运算符:按位与(&)、按位或(|)、按位异或(^)、按位取反(~)等

•赋值运算符:=及其与其它运算符结合的扩展赋值运算符,例如+=、%=

操作符优先级:算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符

在Scala中,操作符就是方法

例如,5 + 3和(5).+(3)是等价的

  1. scala> val sum1 = 5 + 3 //实际上调用了 (5).+(3)
  2. sum1: Int = 8
  3. scala> val sum2 = (5).+(3) //可以发现,写成方法调用的形式,和上面得到相同的结果
  4. sum2: Int = 8

富包装类

•对于基本数据类型,除了以上提到的各种操作符外,Scala还提供了许多常用运算的方法,只是这些方法不是在基本类里面定义,而是被封装到一个对应的富包装类中

•每个基本类型都有一个对应的富包装类,例如Int有一个RichInt类、String有一个RichString类,这些类位于包scala.runtime中

•当对一个基本数据类型的对象调用其富包装类提供的方法,Scala会自动通过隐式转换将该对象转换为对应的富包装类型,然后再调用相应的方法。例如:3 max 5

3.变量

Scala有两种类型的变量:

–val:是不可变的,在声明时就必须被初始化,而且初始化以后就不能再赋值

–var:是可变的,声明的时候需要进行初始化,初始化以后还可以再次对其赋值

基本语法:

val 变量名:数据类型 = 初始值

var 变量名:数据类型 = 初始值

类型推断机制(type inference):根据初始值自动推断变量的类型,使得定义变量时可以省略具体的数据类型及其前面的冒号

  1. scala> val myStr = "Hello World!"
  2. myStr: String = Hello World!

当然,我们也可以显式声明变量的类型:

  1. scala> val myStr2 : String = "Hello World!"
  2. myStr2: String = Hello World!

myStr是val变量,因此,一旦初始化以后,就不能再次赋值

  1. scala> myStr = "Hello Scala!"
  2. <console>:27: error: reassignment to val
  3. myStr = "Hello Scala!"
  4. ^

var变量初始化以后,可以再次赋值

  1. scala> var myPrice : Double = 9.9
  2. myPrice: Double = 9.9
  3. scala> myPrice = 10.6
  4. myPrice: Double = 10.6

注意:在REPL环境下,可以重复使用同一个变量名来定义变量,而且变量前的修饰符和其类型都可以不一致,REPL会以最新的一个定义为准

  1. scala> val a = "Xiamen University"
  2. a: String = Xiamen University
  3. scala> var a = 50
  4. a: Int = 50

2.2 输入输出

1.控制台输入输出语句

•从控制台读入数据方法:readInt、readDouble、readByte、readShort、readFloat、readLong、readChar readBoolean及readLine,分别对应9种基本数据类型,其中前8种方法没有参数,readLine可以不提供参数,也可以带一个字符串参数的提示所有这些函数都属于对象scala.io.StdIn的方法,使用前必须导入,或者直接用全称进行调用

从控制台读入数据方法

  1. scala> import io.StdIn._
  2. import io.StdIn._
  3. scala> var i=readInt() //此时键盘输入54
  4. i: Int = 54
  5. scala> var f=readFloat //此时键盘输入1.618
  6. f: Float = 1.618
  7. scala> var b=readBoolean //此时键盘输入true
  8. b: Boolean = true
  9. scala> var str=readLine("please input your name:")
  10. please input your name: //此时键盘输入Li Lei
  11. str: String = Li Lei

向控制台输出信息方法:

•print()和println(),可以直接输出字符串或者其它数据类型,其中println在末尾自动换行。

  1. scala> val i=345
  2. i: Int = 345
  3. scala> print("i=");print(i)
  4. //两条语句位于同一行,不能省略中间的分号
  5. i=345
  6. scala> println("hello");println("world")
  7. hello
  8. world
  9. # C语言风格格式化字符串的printf()函数
  10. scala> val i = 34
  11. i: Int = 34
  12. scala> val f=56.5
  13. f: Double = 56.5
  14. scala> printf("I am %d years old and weight %.1f Kg.",i,f)
  15. I am 34 years old and weight 56.5 Kg.

print()、println()和printf() 都在对象Predef中定义,该对象默认情况下被所有Scala程序引用,因此可以直接使用Predef对象提供的方法,而无需使用scala.Predef.的形式。

s字符串和f字符串:Scala提供的字符串插值机制,以方便在字符串字面量中直接嵌入变量的值。

基本语法:s “ …1.Scala编程 - 图1变量名%格式化字符… “

  1. scala> val i=10
  2. i: Int = 10
  3. scala> val f=3.5
  4. f: Double = 3.5452
  5. scala> val s="hello"
  6. s: String = hello
  7. scala> println(s"$s:i=$i,f=$f") //s插值字符串
  8. hello:i=10,f=3.5452
  9. scala> println(f"$s:i=$i%-4d,f=$f%.1f") //f插值字符串
  10. hello:i=10 ,f=3.5
  11. scala> val x=10
  12. x: Int = 10
  13. scala> val y=10.6
  14. y: Double = 10.6
  15. scala> val z="value"
  16. z: String = value
  17. scala> println(s"$z:x=$x,y=$y")
  18. value:x=10,y=10.6
  19. scala> println(f"$z:x=$x%-4d,y=$y%.0f")
  20. value:x=10 ,y=11

2.读写文件

写入文件

Scala需要使用java.io.PrintWriter实现把数据写入到文件,PrintWriter类提供了print 和println两个写方法

  1. scala> import java.io.PrintWriter
  2. scala> val outputFile = new PrintWriter("test.txt")
  3. scala> outputFile.println("Hello World")
  4. scala> outputFile.print("Spark is good")
  5. scala> outputFile.close()

读取文件

可以使用scala.io.Source的getLines方法实现对文件中所有行的读取

2.3 控制结构

1.if条件表达式

有一点与Java不同的是,Scala中的if表达式的值可以赋值给变量

  1. scala> val n = 6
  2. n: Int = 6
  3. scala> val nn = if(n>0) 999 else -1
  4. nn: Int = 999

2.while循环

  1. while(true){
  2. }
  3. do{
  4. }while(true)

3.for循环

基本语法 for (变量 <- 表达式) {语句块} 其中,“变量<-表达式”被称为“生成器(generator)”

  1. scala> for(i <- 1 to 5 by 2) println(i)
  2. 1
  3. 3
  4. 5

•“守卫(guard)”的表达式:过滤出一些满足条件的结果。基本语法:

for (变量 <- 表达式 if 条件表达式) 语句块

  1. scala> for(i <- 1 to 5 if i%2==0) println(i)
  2. 2
  3. 4

Scala也支持“多个生成器”的情形,可以用分号把它们隔开,比如:

  1. scala> for(i <- 1 to 5 if i%2==0;j <- 1 to 3) println(i*j)
  2. 2
  3. 4
  4. 6
  5. 4
  6. 8
  7. 12

for推导式:for结构可以在每次执行的时候创造一个值,然后将包含了所有产生值的集合作为for循环表达式的结果返回,集合的类型由生成器中的集合类型确定 **

for (变量 <- 表达式) yield {语句块}**

  1. #
  2. scala> val array = for(i <- Array(1,2,3,4,5,6) if i%2==0) yield {println(i);i}
  3. 2
  4. 4
  5. 6
  6. array: Array[Int] = Array(2, 4, 6)
  7. #
  8. scala> val array = for(i <- Array(1,2,3,4,5,6) if i%2==0) yield {println(i)}
  9. 2
  10. 4
  11. 6
  12. array: Array[Unit] = Array((), (), ())
  13. #
  14. scala> val array = for(i <- Array(1,2,3,4,5,6) if i%2==0) yield {i}
  15. array: Array[Int] = Array(2, 4, 6)

4.异常处理

Scala不支持Java中的“受检查异常”(checked exception),将所有异常都当作“不受检异常”(或称为运行时异常)

Scala仍使用try-catch结构来捕获异常

  1. import java.io.FileReader
  2. import java.io.FileNotFoundException
  3. import java.io.IOException
  4. try {
  5. val f = new FileReader("input.txt")
  6. // 文件操作
  7. } catch {
  8. case ex: FileNotFoundException =>
  9. // 文件不存在时的操作
  10. case ex: IOException =>
  11. // 发生I/O错误时的操作
  12. } finally {
  13. file.close() // 确保关闭文件
  14. }

受检查异常和不受检查异常的区别:https://www.cnblogs.com/tjudzj/p/7053980.html

异常,开发者之间一直存在着争议,毕竟两类异常都各有优缺点。受检异常的特点在于它强制要求开发人员在代码中进行显式的声明和捕获,否则就会产生编译错误。这种限制从好的方面来说,可以防止开发人员意外地忽略某些出错的情况,因为编译器不允许出现未被处理的受检异常;从不好的方面来说,受检异常对程序中的设计提出了更高的要求。不恰当地使用受检异常,会使代码中充斥着大量没有实际作用、只是为了通过编译而添加的代码。而非受检异常的特点是,如果不捕获异常,不会产生编译错误,异常会在运行时刻才被抛出。

非受检异常的好处是可以去掉一些不需要的异常处理代码,而不好之处是开发人员可能忽略某些应该处理的异常。一个典型的例子是把字符串转换成数字时会发生java.lang.NumberFormatException异常,忽略该异常可能导致一个错误的输入就造成整个程序退出。

目前的主流意见是,最好优先使用非受检异常。

5.对循环的控制

•为了提前终止整个循环或者跳到下一个循环,Scala没有break和continue关键字

•Scala提供了一个Breaks类(位于包scala.util.control)。Breaks类有两个方法用于对循环结构进行控制,即breakable和break

  1. breakable{
  2. ...
  3. if(...) break
  4. ...
  5. }
  6. #
  7. //代码文件为/usr/local/scala/mycode/TestBreak.scala
  8. import util.control.Breaks._ //导入Breaks类的所有方法
  9. val array = Array(1,3,10,5,4)
  10. breakable{
  11. for(i<- array){
  12. if(i>5) break //跳出breakable,终止for循环,相当于Java中的break
  13. println(i)
  14. }
  15. }
  16. // 上面的for语句将输出1,3
  17. for(i<- array){
  18. breakable{
  19. if(i>5) break //跳出breakable,终止当次循环,相当于Java的continue
  20. println(i)
  21. }
  22. }// 上面的for语句将输出1,3,5,4

2.4 数据结构

1.数组(Array)

数组:一种可变的、可索引的、元素具有相同类型的数据集合

Scala提供了参数化类型的通用数组类Array[T],其中T可以是任意的Scala类型,可以通过显式指定类型或者通过隐式推断来实例化一个数组

  1. # 声明一个整型数组
  2. val intValueArr = new Array[Int](3) //声明一个长度为3的整型数组,每个数组元素初始化为0
  3. intValueArr(0) = 12 //给第1个数组元素赋值为12
  4. intValueArr(1) = 45 //给第2个数组元素赋值为45
  5. intValueArr(2) = 33 //给第3个数组元素赋值为33
  6. # 声明一个字符串数组
  7. val myStrArr = new Array[String](3) //声明一个长度为3的字符串数组,每个数组元素初始化为null
  8. myStrArr(0) = "BigData"
  9. myStrArr(1) = "Hadoop"
  10. myStrArr(2) = "Spark"
  11. for (i <- 0 to 2) println(myStrArr(i))

可以不给出数组类型,Scala会自动根据提供的初始化数据来推断出数组的类型

  1. val intValueArr = Array(12,45,33)
  2. val myStrArr = Array("BigData","Hadoop","Spark")

多维数组的创建:调用Array的ofDim方法

  1. val myMatrix = Array.ofDim[Int](3,4) //类型实际就是Array[Array[Int]]
  2. val myCube = Array.ofDim[String](3,2,4) //类型实际是Array[Array[Array[Int]]]

可以使用多级圆括号来访问多维数组的元素,例如myMatrix(0)(1)返回第一行第二列的元素

2.元组(Tuple)

元组是对多个不同类型对象的一种简单封装。定义元组最简单的方法就是把多个元素用逗号分开并用圆括号包围起来。

使用下划线“_”加上从1开始的索引值,来访问元组的元素

  1. scala> val tuple = ("BigData",2020,308.4)
  2. tuple: (String, Int, Double) = (BigData,2020,308.4)
  3. scala> println(tuple._1)
  4. BigData
  5. scala> println(tuple._2)
  6. 2020
  7. scala> println(tuple._3)
  8. 308.4

如果需要在方法里返回多个不同类型的对象,Scala可以通过返回一个元组来实现

3.容器(Collection)

•Scala提供了一套丰富的容器(collection)库,包括序列(Sequence)、集合(Set)、映射(Map)等

•Scala用了三个包来组织容器类,分别是scala.collection 、scala.collection.mutable和scala.collection.immutable

•scala.collection封装了可变容器和不可变容器的超类或特质,定义了可变容器和不可变容器的一些通用操作

scala.collection包中容器的宏观层次结构

所有容器的根为Traverable特质,表示可遍历的,它为所有的容器类定义了抽象的foreach方法,该方法用于对容器元素进行遍历操作。混入Traverable特质的容器类必须给出foreach方法的具体实现。Traverable的下一级为Iterable特质,表示元素可一个个地依次迭代,该特质定义了一个抽象的iterator方法,混入该特质的容器必须实现iterator方法,返回一个迭代器(Iterator),另外,Iterable特质还给出了其从Traverable继承的foreach方法的一个默认实现,即通过迭代器进行遍历。

在Iterable下的继承层次包括三个特质,分别是序列(Seq)、映射(Map)和 集合(Set),这三种容器最大的区别是其元素的索引方式,序列是按照从0开始的整数进行索引的,映射是按照键值进行索引的,而集合是没有索引的。

4.序列(Sequence)

序列(Sequence): 元素可以按照特定的顺序访问的容器。序列中每个元素均带有一个从0开始计数的固定索引位置

序列容器的根是collection.Seq特质。其具有两个子特质 LinearSeq和IndexedSeq。LinearSeq序列具有高效的 head 和 tail 操作,而IndexedSeq序列具有高效的随机存储操作

实现了特质LinearSeq的常用序列有列表(List)和队列(Queue)。实现了特质IndexedSeq的常用序列有可变数组(ArrayBuffer)和向量(Vector)

1.序列(Sequence)——列表(List)

•列表: 一种共享相同类型的不可变的对象序列。定义在scala.collection.immutable包中

•不同于Java的java.util.List,scala的List一旦被定义,其值就不能改变,因此声明List时必须初始化

  1. scala> var strList = List("Hadoop","Spark","HBase","Hive")
  2. strList: List[String] = List(Hadoop, Spark, HBase, Hive)

•列表有头部和尾部的概念,可以分别使用head和tail方法来获取

•head返回的是列表第一个元素的值

•tail返回的是除第一个元素外的其它值构成的新列表,这体现出列表具有递归的链表结构

•strList.head将返回字符串”BigData”,strList.tail返回List (“Hadoop”,”Spark”)

不能用new来建立List(原型:sealed abstract class List[+A]

补充相同类型:对于包括List在内的所有容器类型,如果没有显式指定元素类型,Scala会自动选择所有初始值的最近公共类型来作为元素的类型。因为Scala的所有对象都来自共同的根Any,因此,原则上容器内可以容纳任意不同类型的成员。例如:val x=List(1,3.4,”Spark”)

构造列表常用的方法是通过在已有列表前端增加元素,使用的操作符为::,例如:

  1. # 执行该语句后strList保持不变,而newList将成为一个新的列表:
  2. scala> val newList = "ETL"::strList
  3. newList: List[String] = List(ETL, Hadoop, Spark, HBase, Hive)

Scala还定义了一个空列表对象Nil,借助Nil,可以将多个元素用操作符::串起来初始化一个列表

  1. scala> val oneList = (1,2,3)
  2. oneList: (Int, Int, Int) = (1,2,3)
  3. scala> val twoList = 1::2::3::Nil
  4. twoList: List[Int] = List(1, 2, 3)

注意:除了head、tail操作是常数时间O(1),其它按索引访问的操作都需要从头开始遍历,因此是线性时间复杂度O(N)。

::是向右结合的(:结尾的操作符都是向右结合)

2.序列(Sequence)——向量(Vector)

Vetor可以实现所有访问操作都是常数时间。

  1. scala> val v = Vector(1,2)
  2. v: scala.collection.immutable.Vector[Int] = Vector(1, 2)
  3. scala> val v1 = 3+:4+:v
  4. v1: scala.collection.immutable.Vector[Int] = Vector(3, 4, 1, 2)
  5. scala> val v2 = v1:+5
  6. v2: scala.collection.immutable.Vector[Int] = Vector(3, 4, 1, 2, 5)
  7. scala> v2(2)
  8. res0: Int = 1

3.序列(Sequence)——Range

• Range类:一种特殊的、带索引的不可变数字等差序列。其包含的值为从给定起点按一定步长增长(减小)到指定终点的所有数值

• Range可以支持创建不同数据类型的数值序列,包括Int、Long、Float、Double、Char、BigInt和BigDecimal等

(1)创建一个从1到5的数值序列,包含区间终点5,步长为1

  1. scala> 1 to 5
  2. res1: scala.collection.immutable.Range.Inclusive = Range 1 to 5
  3. scala> 1.to(5)
  4. res2: scala.collection.immutable.Range.Inclusive = Range 1 to 5
  5. scala> 1 until 5
  6. res7: scala.collection.immutable.Range = Range 1 until 5
  7. scala> 1 to 10 by 2
  8. res3: scala.collection.immutable.Range = inexact Range 1 to 10 by 2

5.集合(Set)

•集合(set):不重复元素的容器(collection)

•列表(List)中的元素是按照插入的先后顺序来组织的,但是,“集合”中的元素并不会记录元素的插入顺序,而是以“哈希”方法对元素的值进行组织,所以,它允许你快速地找到某个元素

•集合包括可变集和不可变集,分别位于scala.collection.mutable包和scala.collection.immutable包,缺省情况下创建的是不可变集

  1. scala> var mySet = Set("Hadoop","Spark")
  2. mySet: scala.collection.immutable.Set[String] = Set(Hadoop, Spark)
  3. scala> mySet += "Scala"
  4. scala> println(mySet)
  5. Set(Hadoop, Spark, Scala)

如果要声明一个可变集,则需要提前引入scala.collection.mutable.Set

  1. scala> import scala.collection.mutable.Set
  2. import scala.collection.mutable.Set
  3. scala> val myMSet = Set("HBase","Hive")
  4. myMSet: scala.collection.mutable.Set[String] = HashSet(Hive, HBase)
  5. scala> myMSet += "MySQL"
  6. res10: myMSet.type = HashSet(Hive, HBase, MySQL)
  7. scala> println(myMSet)
  8. HashSet(Hive, HBase, MySQL)

6.映射(Map)

•映射(Map):一系列键值对的容器。键是唯一的,但值不一定是唯一的。可以根据键来对值进行快速的检索

•Scala 的映射包含了可变的和不可变的两种版本,分别定义在包scala.collection.mutable 和scala.collection.immutable 里。默认情况下,Scala中使用不可变的映射。如果想使用可变映射,必须明确地导入scala.collection.mutable.Map

其中,操作符”->”是定义二元组的简写方式,它会返回一个包含调用者和传入参数的二元组

  1. scala> val word = Map("a"->"about","b"->"book","c"->"count")
  2. word: scala.collection.immutable.Map[String,String] = Map(a -> about, b -> book, c -> count)
  3. scala> println(word("a"))
  4. about
  5. scala> val a = word("d")
  6. java.util.NoSuchElementException: key not found: d
  7. at scala.collection.immutable.Map$Map3.apply(Map.scala:335)
  8. ... 28 elided
  9. scala> val a = if(word.contains("a")) word("a") else 0
  10. a: Any = about
  11. scala> println(a)
  12. about

可变的映射

  1. # 默认为不可变
  2. scala> word("a")= "apple"
  3. ^
  4. error: value update is not a member of scala.collection.immutable.Map[String,String]
  5. did you mean updated?
  6. scala> import scala.collection.mutable.Map
  7. import scala.collection.mutable.Map
  8. scala> val wordM = Map("a"->"all","b"->"bit","c"->"cow")
  9. wordM: scala.collection.mutable.Map[String,String] = HashMap(a -> all, b -> bit, c -> cow)
  10. # key存在则变更
  11. scala> wordM("a")="abc"
  12. scala> println(wordM)
  13. HashMap(a -> abc, b -> bit, c -> cow)
  14. #key不存在则添加
  15. scala> wordM("d")="dog"
  16. scala> println(wordM)
  17. HashMap(a -> abc, b -> bit, c -> cow, d -> dog)
  18. # 也可以使用+=操作来添加新的元素
  19. scala> wordM += ("e"->"element")
  20. res19: wordM.type = HashMap(a -> abc, b -> bit, c -> cow, d -> dog, e -> element)
  21. scala> wordM += ("f"->"flow","g"->"google")
  22. ^
  23. warning: method += in trait Growable is deprecated (since 2.13.0): Use `++=` (addAll) instead of varargs `+=`
  24. res20: wordM.type = HashMap(a -> abc, b -> bit, c -> cow, d -> dog, e -> element, f -> flow, g -> google)

7.迭代器(Iterator)

•迭代器(Iterator)不是一个容器,而是提供了按顺序访问容器元素的数据结构

• 迭代器包含两个基本操作:next和hasNext。next可以返回迭代器的下一个元素,hasNext用于检测是否还有下一个元素

  1. scala> val iter = Iterator("Hadoop","Spark","Scala")
  2. iter: Iterator[String] = <iterator>
  3. scala> while(iter.hasNext){println(iter.next())}
  4. Hadoop
  5. Spark
  6. Scala

1、尽管构造一个迭代器与构造一个容器很类似,但迭代器并不是一个容器类,因为不能随机访问迭代器的元素,而只能按从前往后的顺序依次访问其元素。

2、实际上,迭代器的大部分方法都会改变迭代器的状态,例如,调用length方法会返回迭代器元素的个数,但是,调用结束后,迭代器已经没有元素了,再次进行相关操作会报错。(举例)

3 面向对象编程基础

作为一个运行一个JVM上的语言,Scala毫无疑问首先是面向对象的语言。尽管在具体的数据处理部分,函数式编程在Scala中已成为首选方案,但在上层的架构组织上,仍然需要采用面向对象的模型,这对于大型的应用程序尤其必不可少。

3.1 类

  1. 类的定义
  1. class Counter{
  2. //这里定义类的字段和方法
  3. }

字段定义:用val或var关键字进行定义

方法定义:

  1. def 方法名(参数列表):返回结果类型={方法体}
  2. class Counter {
  3. var value = 0
  4. def increment(step:Int):Unit = { value += step}
  5. def current():Int = {value}
  6. }

使用new关键字创建一个类的实例

  1. val myCounter = new Counter
  2. myCounter.value = 5 //访问字段
  3. myCounter.increment(3) //调用方法
  4. println(myCounter.current) //调用无参数方法时,可以省略方法名后的括号
  1. 类成员的可见性

•Scala类中所有成员的默认可见性为公有,任何作用域内都能直接访问公有成员

•除了默认的公有可见性,Scala也提供private和protected,其中,private成员只对本类型和嵌套类型可见;protected成员对本类型和其继承类型都可见

为了避免直接暴露public字段,建议将字段设置为private,对于private字段,Scala采用类似Java中的getter和setter方法,定义了两个成对的方法value和value_=进行读取和修改

  1. scala> :load /usr/local/scala/mycode/Counter.scala
  2. Loading /usr/local/scala/mycode/Counter.scala
  3. defined class Counter
  4. scala> val myCounter = new Counter
  5. myCounter: Counter = Counter@f591271
  6. scala> myCounter.value_=(3) //为privateValue设置新的值
  7. scala> println(myCounter.value)//访问privateValue的当前值
  8. 3

Scala语法中有如下规范,当编译器看到以value和value=这种成对形式出现的方法时,它允许用户去掉下划线,而采用类似赋值表达式的形式

  1. myCounter.value= 3 // 等效于myCounter.value_=(3)

如果class Counter{}中,使用 private var value =0,那么,使用scalac命令编译该程序,会出现myCounter.value=3变量无法访问的错误,因为是私有变量,不能从外部访问。

  1. 方法的定义方式

基本语法:

  1. def 方法名(参数列表):返回结果类型={方法体}

•方法参数前不能加上val或var,所有的方法参数都是不可变类型

•无参数的方法定义时可以省略括号,这时调用时也不能带有括号;如果定义时带有括号,则调用时可以带括号,也可以不带括号

•方法名后面的圆括号()可以用大括号{}来代替

•如果方法只有一个参数,可以省略点号(.)而采用中缀操作符调用方法

•如果方法体只有一条语句,可以省略方法体两边的大括号

  1. scala> :load /usr/local/scala/mycode/Counter1.scala
  2. Loading /usr/local/scala/mycode/Counter1.scala
  3. defined class Counter
  4. scala> val c=new Counter
  5. c: Counter = Counter@30ab4b0e
  6. scala> c increment 5 //中缀调用法
  7. scala> c.getValue() //getValue定义中有括号,可以带括号调用
  8. res0: Int = 0
  9. scala> c.getValue // getValue定义中有括号,也可不带括号调用
  10. res1: Int = 0
  11. scala> c.current() // current定义中没有括号,不可带括号调用
  12. <console>:13: error: Int does not take parameters
  13. c.current()
  14. ^
  15. scala> c.current // current定义中没有括号,只能不带括号调
  16. res3: Int = 0

•当方法的返回结果类型可以从最后的表达式推断出时,可以省略结果类型

•如果方法返回类型为Unit,可以同时省略返回结果类型和等号,但不能省略大括号

  1. class Counter {
  2. var value = 0
  3. def increment(step:Int) { value += step }//赋值表达式的值为Unit类型
  4. def current()= value //根据value的类型自动推断出返回类型为Int型
  5. }
  1. 构造器

•Scala类的定义主体就是类的构造器,称为主构造器。在类名之后用圆括号列出主构造器的参数列表

•主构造器的参数前可以使用val或var关键字,Scala内部将自动为这些参数创建私有字段,并提供对应的访问方法

  1. scala> class Counter(var name:String) //定义一个带字符串参数的简单类
  2. defined class Counter
  3. scala> var mycounter = new Counter("Runner")
  4. mycounter: Counter = Counter@17fcc4f7
  5. scala> println(mycounter.name) //调用读方法
  6. Runner
  7. scala> mycounter.name_=("Timer") //调用写方法
  8. scala> mycounter.name = "Timer"// 更直观地调用写方法,和上句等效
  9. mycounter.name: String = Timer

• 如果不希望将构造器参数成为类的字段,只需要省略关键字var或者val

•Scala类可以包含零个或多个辅助构造器(auxiliary constructor)。辅助构造器使用this进行定义,this的返回类型为Unit

•每个辅助构造器的第一个表达式必须是调用一个此前已经定义的辅助构造器或主构造器,调用的形式为“this(参数列表)”

  1. //代码文件为/usr/local/scala/mycode/Counter2.scala
  2. class Counter {
  3. private var value = 0
  4. private var name = ""
  5. private var step = 1 //计算器的默认递进步长
  6. println("the main constructor")
  7. def this(name: String){ //第一个辅助构造器
  8. this() //调用主构造器
  9. this.name = name
  10. printf("the first auxiliary constructor,name:%s\n",name)
  11. }
  12. def this (name: String,step: Int){ //第二个辅助构造器
  13. this(name) //调用前一个辅助构造器
  14. this.step = step
  15. printf("the second auxiliary constructor,name:%s,step:%d\n",name,step)
  16. }
  17. def increment(step: Int): Unit = { value += step}
  18. def current(): Int = {value}
  19. }
  20. scala> :load /usr/local/scala/mycode/Counter2.scala
  21. Loading /usr/local/scala/mycode/Counter2.scala
  22. defined class Counter
  23. scala> val c1=new Counter
  24. the main constructor
  25. c1: Counter = Counter@319c6b2
  26. scala> val c2=new Counter("the 2nd Counter")
  27. the main constructor
  28. the first auxiliary constructor,name:the 2nd Counter
  29. c2: Counter = Counter@4ed6c602
  30. scala> val c3=new Counter("the 3rd Counter",2)
  31. the main constructor
  32. the first auxiliary constructor,name:the 3rd Counter
  33. the second auxiliary constructor,name:the 3rd Counter,step:2
  34. c3: Counter = Counter@64fab83b

3.2 对象

1.单例对象

2.apply方法

3.update方法

4.unapply方法

3.3 继承

\1. 抽象类

\2. 扩展类

\3. Scala的类层次结构

\4. Option类

3.4 特质

\1. 特质概述

\2. 特质的定义

\3. 把特质混入类中

\4. 把多个特质混入类中

3.5 模式匹配

\1.match语句

\2. case类

3.6 包

\1. 包的定义

\2. 引用包成员

4 函数式编程基础

4.1 函数定义与使用

4.2 高阶函数

4.3 针对容器的操作

4.4 函数式编程实例WordCount