第2章Scala语言基础
2.1 Scala语言概述
2.2 Scala基础知识
2.3 Scala面向对象编程基础
2.4 Scala函数式编程基础
2.1 Scala语言概述
2.1.1 计算机的缘起
2.1.2 编程范式
2.1.3 Scala简介
2.1.1 计算机的缘起
- 数学家阿隆佐•邱奇(Alonzo Church)设计了“λ演算”,这是一套用于研究函数定义、函数应用和递归的形式系统
- λ演算被视为最小的通用程序设计语言
- λ演算的通用性就体现在,任何一个可计算函数都能用这种形式来表达和求值
- 演算是一个数理逻辑形式系统,强调的是变换规则的运用,而非实现它们的具体机器
- 英国数学家阿兰·图灵采用了完全不同的设计思路,提出了一种全新的抽象计算模型——图灵机
- 图灵机是现代计算机的鼻祖。现有理论已经证明,λ演算和图灵机的计算能力是等价的
- 冯·诺依曼(John Von Neumann)将图灵的理论物化成为实际的物理实体,成为了计算机体系结构的奠基者
- 1945年6月,冯·诺依曼提出了在数字计算机内部的存储器中存放程序的概念,这是所有现代计算机的范式,被称为“冯·诺依曼结构”
2.1.2 编程范式 - 编程范式是指计算机编程的基本风格或典范模式。常见的编程范式主要包括命令式编程和函数式编程。面向对象编程就属于命令式编程,比如C++、Java等
- 命令式语言是植根于冯·诺依曼体系的,一个命令式程序就是一个冯·诺依曼机的指令序列,给机器提供一条又一条的命令序列让其原封不动地执行
- 函数式编程,又称泛函编程,它将计算机的计算视为数学上的函数计算
- 函数编程语言最重要的基础是λ演算。典型的函数式语言包括Haskell、Erlang和Lisp等
- 一个很自然的问题是,既然已经有了命令式编程,为什么还需要函数式编程呢?
- 为什么在C++、Java等命令式编程流行了很多年以后,近些年函数式编程会迅速升温呢?
- 命令式编程涉及多线程之间的状态共享,需要锁机制实现并发控制
- 函数式编程不会在多个线程之间共享状态,不需要用锁机制,可以更好并行处理,充分利用多核CPU并行处理能力
2.1.3 Scala简介
Scala是一门类Java的多范式语言,它整合了面向对象编程和函数式编程的最佳特性。 - Scala运行于Java虚拟机(JVM)之上,并且兼容现有的Java程序
- Scala是一门纯粹的面向对象的语言
- Scala也是一门函数式语言
2.1.4 Scala的安装
(1)Linux系统的安装 - Scala运行在Java虚拟机(JVM)之上,因此只要安装有相应的Java虚拟机,所有的操作系统都可以运行Scala程序,包括Window、Linux、Unix、Mac OS等。后续的Spark操作都是在Linux系统下进行的
- Scala的详细安装教程可以参考此链接
(2)在Linux系统中安装Java
第1种安装方式:直接通过命令安装 OpenJDK 7
sudo apt-get install openjdk-7-jre openjdk-7-jdk
配置 JAVA_HOME 环境变量
vim ~/.bashrc
使配置立即生效:
source ~/.bashrc # 使变量设置生效
第2种安装方式:直接通过命令安装 default-jdk
sudo apt-get install default-jre default-jdk
配置 JAVA_HOME 环境变量
vim ~/.bashrc
在文件最前面添加如下单独一行(注意,等号“=”前后不能有空格),然后保存退出:
export JAVA_HOME=/usr/lib/jvm/default-java
使配置立即生效:
source ~/.bashrc # 使变量设置生效
检验一下是否设置正确
echo $JAVA_HOME # 检验变量值
java -version
$JAVA_HOME/bin/java -version # 与直接执行 java -version 一样
如果设置正确的话,$JAVA_HOME/bin/java -version 会输出 java 的版本信息,且和 java -version 的输出结果一样
(3)安装Scala
Spark可在Java 8,Python 2.7 + / 3.4 +和R 3.1+上运行。对于Scala API,Spark2.4.5使用Scala 2.12。您将需要使用兼容的Scala版本(2.12.x)。
请注意,自Spark 2.2.0起已删除了对Java 7,Python 2.6和2.6.5之前的旧Hadoop版本的支持。从2.3.0版本开始,不再支持Scala 2.10。从Spark 2.4.1开始不支持Scala 2.11,它将在Spark 3.0中删除。
-这边使用的Spark版本是2.4.5,其对应的Scala版本是2.12.11
登录Scala官网,下载scala-2.12.11.tgz
sudo tar -zxf scala-2.12.11.tgz -C /usr/local # 解压到/usr/local中
cd /usr/local/
sudo mv ./scala-2.12.11/ ./scala # 将文件夹名改为scala
sudo chown -R hadoop ./scala # 修改文件权限,用hadoop用户拥有对scala目录的权限
//如果之前没有创建hadoop用户可先创建hadoop用户
powershell sudo useradd -m hadoop -s /bin/bash #这条命令创建了可以登陆的hadoop 用户,并使用 /bin/bash 作为 shell
sudo passwd hadoop #设置hadoop密码
sudo adduser hadoop sudo #为 hadoop 用户增加管理员权限
把scala命令添加到path环境变量中
vim ~/.bashrc
export PATH=$PATH:/usr/local/scala/bin
使配置立即生效:
source ~/.bashrc # 使变量设置生效
检测配置是否成功:
scala -version
启动Scala解释器:
scala
scala> //可以在命令提示符后面输入命令
2.1.5 HelloWorld
(1)通过HelloWorld程序了解Scala解释器的使用方法
在Shell命令提示符界面中输入“scala”命令后,会进入scala命令行提示符状态:
scala> //可以在命令提示符后面输入命令
其中的换行|按Ctrl+Eenter键即可实现
使用命令“:quit”退出Scala解释器,如下所示:
scala> :quit
(2)在Scala解释器中运行脚本文件
用“:load”命令导入脚本,一次运行多行程序:
使用文本编辑器(比如vim)创建一个代码文件Test.scala
//代码文件为/usr/local/scala/mycode/Test.scala
println("This is the first line")
println("This is the second line")
println("This is the third line")
在Scala REPL中执行如下命令运行该代码文件:
scala> :load /usr/local/scala/mycode/Test.scala
Loading /usr/local/scala/mycode/Test.scala…
This is the first line
This is the second line
This is the third line
(3)通过编译打包的方式运行Scala程序
//代码文件为/usr/local/scala/mycode/HelloWorld.scala
object HelloWorld {
def main(args: Array[String]) {
println("Hello, world!");
}
}
使用scalac命令进行编译(编译的结果为Java字节码)
cd /usr/local/scala/mycode
scalac HelloWorld.scala
使用scala或者java命令运行字节码文件
scala -classpath . HelloWorld
java -classpath .:/usr/local/scala/lib/scala-library.jar HelloWorld
2.2 Scala基础知识
2.2.1基本数据类型和变量
2.2.2 输入输出
2.2.3 控制结构
2.2.4 数据结构
2.2.1基本数据类型和变量
- 1.基本数据类型
- 2.基本操作
- 3.变量
1. 基本数据类型
- Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double和Boolean(注意首字母大写)
- 和Java不同的是,在Scala中,这些类型都是“类”,并且都是包scala的成员,比如,Int的全名是scala.Int。对于字符串,Scala用java.lang.String类来表示字符串
2.字面量(literal)
val i = 123 //123就是整数字面量
val i = 3.14 //3.14就是浮点数字面量
val i = true //true就是布尔型字面量
val i = 'A' //'A'就是字符字面量
val i = “Hello” //“Hello”就是字符串字面量
3.操作符
- 算术运算符:加(+)、减(-) 、乘(*) 、除(/) 、余数(%);
- 关系运算符:大于(>)、小于(<)、等于(==)、不等于(!=)、大于等于(>=)、小于等于(<=)
- 逻辑运算符:逻辑与(&&)、逻辑或(||)、逻辑非(!);
- 位运算符:按位与(&)、按位或(|)、按位异或(^)、按位取反(~)等
- 赋值运算符:=及其与其它运算符结合的扩展赋值运算符,例如+=、%=
操作符优先级:算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符
在Scala中,操作符就是方法
例如,5 + 3和(5).+(3)是等价的
a 方法 b 等价于 a.方法(b)
scala> val sum1 = 5 + 3 //实际上调用了 (5).+(3)
sum1: Int = 8
scala> val sum2 = (5).+(3) //可以发现,写成方法调用的形式,和上面得到相同的结果
sum2: Int = 8
3.操作符——富包装类
- 对于基本数据类型,除了以上提到的各种操作符外,Scala还提供了许多常用运算的方法,只是这些方法不是在基本类里面定义,而是被封装到一个对应的富包装类中
- 每个基本类型都有一个对应的富包装类,例如Int有一个RichInt类、String有一个RichString类,这些类位于包scala.runtime中
- 当对一个基本数据类型的对象调用其富包装类提供的方法,Scala会自动通过隐式转换将该对象转换为对应的富包装类型,然后再调用相应的方法。例如:3 max 5
4.变量
Scala有两种类型的变量:
-
val:是不可变的,在声明时就必须被初始化,而且初始化以后就不能再赋值
-
var:是可变的,声明的时候需要进行初始化,初始化以后还可以再次对其赋值
基本语法:
val 变量名:数据类型 = 初始值
var 变量名:数据类型 = 初始值
类型推断机制(type inference):根据初始值自动推断变量的类型,使得定义变量时可以省略具体的数据类型及其前面的冒号
scala> val myStr = "Hello World!"
myStr: String = Hello World!
当然,我们也可以显式声明变量的类型:
scala> val myStr2 : String = "Hello World!"
myStr2: String = Hello World!
scala> println(myStr)
Hello World!
myStr是val变量,因此,一旦初始化以后,就不能再次赋值
scala> myStr = "Hello Scala!"
<console>:27: error: reassignment to val
myStr = "Hello Scala!"
^
var变量初始化以后,可以再次赋值
scala> var myPrice : Double = 9.9
myPrice: Double = 9.9
scala> myPrice = 10.6
myPrice: Double = 10.6
注意:在REPL环境下,可以重复使用同一个变量名来定义变量,而且变量前的修饰符和其类型都可以不一致,REPL会以最新的一个定义为准
scala> val a = "Xiamen University"
a: String = Xiamen University
scala> var a = 50
a: Int = 50
2.2.2 输入输出
- 1.控制台输入输出语句
- 2.读写文件
1. 控制台输入输出语句
- 从控制台读入数据方法:readInt、readDouble、readByte、readShort、readFloat、readLong、readChar readBoolean及readLine,分别对应9种基本数据类型,其中前8种方法没有参数,readLine可以不提供参数,也可以带一个字符串参数的提示
- 所有这些函数都属于对象scala.io.StdIn的方法,使用前必须导入,或者直接用全称进行调用
从控制台读入数据方法
scala> import io.StdIn._
import io.StdIn._
scala> var i=readInt()
54
i: Int = 54
scala> var f=readFloat
1.618
f: Float = 1.618
scala> var b=readBoolean
true
b: Boolean = true
scala> var str=readLine("please input your name:")
please input your name:Li Lei
str: String = Li Lei
向控制台输出信息方法:
- print()和println(),可以直接输出字符串或者其它数据类型,其中println在末尾自动换行。
scala> val i=345
i: Int = 345
scala> print("i=");print(i)
//两条语句位于同一行,不能省略中间的分号
i=345
scala> println("hello");println("world")
hello
world
- C语言风格格式化字符串的printf()函数
scala> val i = 34
i: Int = 34
scala> val f=56.5
f: Double = 56.5
scala> printf("I am %d years old and weight %.1f Kg.","Li Lie",i,f)
I am 34 years old and weight 56.5 Kg.
print()、println()和printf() 都在对象Predef中定义,该对象默认情况下被所有Scala程序引用,因此可以直接使用Predef对象提供的方法,而无需使用scala.Predef.的形式。
s字符串和f字符串:Scala提供的字符串插值机制,以方便在字符串字面量中直接嵌入变量的值。
基本语法:s " …$变量名… " 或 f " …$变量名%格式化字符… "
scala> val i=10
i: Int = 10
scala> val f=3.5
f: Double = 3.5452
scala> val s="hello"
s: String = hello
scala> println(s"$s:i=$i,f=$f") //s插值字符串
hello:i=10,f=3.5452
scala> println(f"$s:i=$i%-4d,f=$f%.1f") //f插值字符串
hello:i=10 ,f=3.5
2.读写文件
- 写入文件
Scala需要使用java.io.PrintWriter实现把数据写入到文件,PrintWriter类提供了print 和println两个写方法
scala> import java.io.PrintWriter
scala> val outputFile = new PrintWriter("test.txt")
scala> outputFile.println("Hello World")
scala> outputFile.print("Spark is good")
scala> outputFile.close()
- 读取文件
可以使用scala.io.Source的getLines方法实现对文件中所有行的读取
2.2.3 控制结构
- if条件表达式
- while循环
- for循环
- 异常处理
- 对循环的控制
1. if条件表达式
if (表达式) {
语句块1
}
else {
语句块2
}
有一点与Java不同的是,Scala中的if表达式的值可以赋值给变量
2. while循环
while (表达式){
循环体
}
do{
循环体
}while (表达式)
3. for循环
基本语法
for (变量 <- 表达式) {
语句块}
其中,“变量<-表达式”被称为“生成器(generator)”
- “守卫(guard)”的表达式:过滤出一些满足条件的结果。基本语法:
for (变量 <- 表达式 if 条件表达式) 语句块
- Scala也支持“多个生成器”的情形,可以用分号把它们隔开,比如:
- for推导式:for结构可以在每次执行的时候创造一个值,然后将包含了所有产生值的集合作为for循环表达式的结果返回,集合的类型由生成器中的集合类型确定
for (变量 <- 表达式) yield {
语句块}
4. 异常处理
Scala不支持Java中的“受检查异常”(checked exception),将所有异常都当作“不受检异常”(或称为运行时异常)
Scala仍使用try-catch结构来捕获异常
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
try {
val f = new FileReader("input.txt")
// 文件操作
} catch {
case ex: FileNotFoundException =>
// 文件不存在时的操作
case ex: IOException =>
// 发生I/O错误时的操作
} finally {
file.close() // 确保关闭文件
}
5. 对循环的控制
为了提前终止整个循环或者跳到下一个循环,Scala没有break和continue关键字
Scala提供了一个Breaks类(位于包scala.util.control)。Breaks类有两个方法用于对循环结构进行控制,即breakable和break
import util.control.Breaks._ //导入Breaks类的所有方法
val array = Array(1,3,10,5,4)
breakable{
for(i<- array){
if(i>5) break //跳出breakable,终止for循环,相当于Java中的break
println(i)
}
}
// 上面的for语句将输出1,3
for(i<- array){
breakable{
if(i>5) break
//跳出breakable,终止当次循环,相当于Java的continue println(i)
}
}/