1. 初识Scala
背景
Scala是为了解决Java在函数式编程、类型推断、模式匹配等方面的不足而诞生的。它提供了更简洁的语法、更强的类型系统和更丰富的函数式编程特性,使得开发者能够以更高效、更灵活的方式编写代码。在大数据框架spark中,大量使用了scala的语法,所以想看懂spark源码,需要先熟悉scala。
1. 简介
1.1 优势
- 弥补Java的不足:
Scala的创始人Martin Odersky在创建Scala时,对Java的一些特性感到不满,特别是Java在函数式编程方面的支持不足。因此,他希望通过Scala来弥补这些不足,提供一种更加灵活和强大的编程语言。 - 融合面向对象和函数式编程:
Scala旨在融合面向对象编程和函数式编程的精髓。它集成了这两种编程风格的多种特性,使得开发者可以根据具体需求选择最合适的编程方式。这种多范式的特性使得Scala在处理复杂问题时更加灵活和高效。 - 提高开发效率和应用程序的可扩展性:
Scala的设计初衷是提高开发效率和应用程序的可扩展性。通过提供简洁的语法和丰富的函数式编程特性,Scala使得开发者能够更加高效地编写代码。同时,Scala的并发编程支持和高效的通信机制使得它在构建分布式系统时具有显著优势。 - 与Java平台无缝集成:
Scala能够完全与Java进行互操作,这意味着Scala程序可以轻松地调用Java库,同时Java程序也能无缝地使用Scala编写的代码。这一特性使得Scala能够充分利用Java平台的丰富资源和强大生态系统,为开发者提供了广阔的发展空间。 - 满足现代软件开发的需求:
随着软件开发的不断发展,现代应用对于灵活性和性能的需求越来越高。Scala凭借其强大的表达能力和灵活性,在多个领域都有广泛的应用,如大数据处理、Web开发、分布式系统等。这使得Scala成为了一种能够满足现代软件开发需求的编程语言。
2. 下载安装
2.1 安装jdk
scala底层是java,因此,使用scala需要先安装jdk 。java安装网上有很多教程,本文不再赘述,本文选择java版本为1.8 ,建议读者如果想跑文章中的程序选择同样的版本,避免出现意料之外的错误
2.2 下载安装Scala
- 官网下载scala(注意要和java版本对应) https://www.scala-lang.org/download/2.12.20.html
- 解压目录
- 配置环境变量(和java类似)
- win+r 输入cmd 打开命令行验证 输入scala
- idea导入插件:在idea插件市场搜索scala, 下载对应插件
3. 基础语法
3.1 定义变量
//定义val 变量
scala> val name="张三"
name: String = 张三
scala> name="李四"
<console>:12: error: reassignment to val
name="李四"
^
//定义var变量
scala> var name="张三"
name: String = 张三
scala> name="李四"
name: String = 李四
从上面代码案例可以看到,定义变量有两种方式,当定义val 变量时,可以认为定义的是常量,该变量值不可变,当定义的是var 时候,变量为变量,值可以变。
可以看到,定义变量的时候,没有定义类型,scala会做类型推断,我们也可以显示定义变量类型,比如
scala> val name:String ="李四"
name: String = 李四
注意:变量名后面需要跟上冒号,类型首字母必须是大写
3.1.2 定义String变量
这里注意:在定义String变量的时候,当变量值包含双引号等特殊字符的时候,需要做转义
val str:String = "我是一个字符串"
val str2:String = "我是一个\"字符串\""
val str3:String = """我是一个"字符串""""
//可以直接使用三个双引号,当中可以包含所有信息,甚至换行符
3.2 操作符
基本和java一样, 操作符的优先级和java一样
特殊:
- 不支持java中的三元操作符(?:),也不支持+±-
- scala中的所有操作符实质上就是对象上的方法(函数)
eg. val a = 1 a.+(1) - scala中类的函数可以作为操作符来进行操作
eg. 1 to 10 1.to(10)
所有以字母或者数字开头的操作符的优先级只比赋值操作符高 - 基本上所有的操作符都是左连接,但是以":"开头的操作符是右连接的
3.3 表达式
scala> val ss:String = {
| print("asdasd")
| "112"
| }
asdasdss: String = 112
scala表达式的返回值就是最后一行代码的执行结果
3.4 懒执行
关键词:lazy
lazy val price = 66.58
在一开始定义的时候不会初始化值
而是在第一次调用的时候才去初始化
使用的场景比如:读取一些大文本文件的时候,可以定义lazy
3.5 range 关键字使用
在 Scala 中,Range 是一种用于表示整数序列的数据类型。你可以使用 to、until 和 by 等关键字或方法来创建和操作范围(Range)。以下是一些常见的用法:
- 使用 to 关键字创建包含结束值的范围:
val range1 = 1 to 5 // 结果是:Range(1, 2, 3, 4, 5)
- 使用 until 关键字创建不包含结束值的范围:
val range2 = 1 until 5 // 结果是:Range(1, 2, 3, 4)
- 使用 by 关键字创建带有步长的范围:
val range3 = 1 to 10 by 2 // 结果是:Range(1, 3, 5, 7, 9)
val range4 = 10 to 1 by -2 // 结果是:Range(10, 8, 6, 4, 2)
- 将范围转换为其他集合类型(如 List 或 Array):
val listFromRange1 = range1.toList // 结果是:List(1, 2, 3, 4, 5)
val arrayFromRange2 = range2.toArray // 结果是:Array(1, 2, 3, 4)
- 遍历范围:
for (i <- range1) {
println(i)
}
// 输出:
// 1
// 2
// 3
// 4
// 5
- 范围的常用方法:
start:范围的起始值。
end:范围的结束值(对于 until 是不包含的结束值,但对于 to 是包含的)。
step:范围的步长(默认为 1)。
isEmpty:检查范围是否为空。
contains(elem):检查范围是否包含某个元素。
val range = 1 to 5
println(range.start) // 输出:1
println(range.end) // 输出:5
println(range.step) // 输出:1
println(range.isEmpty) // 输出:false
println(range.contains(3)) // 输出:true
4. 判断循环语句
4.1 判断
scala> val age=1
age: Int = 1
scala> if(age > 18) 1 else 0
res1: Int = 0
scala>if(age > 18) "adult" else 0
res3: Any = 0
判断语句基本和java一致,这里主要有想说明两点:
- 第二个语句可以认为是scala的三目表达式
- 第三个语句可以看到,scala会做if语句的类型推断,如果两个类型不同,Scala编译器就会去找两个类型的公共父类Any
4.2 循环
注意:Scala不支持break或continue语句
4.2.1 for循环
注意两个关键字 <- ,to
package com.wanlong
object TestFor {
def main(args: Array[String]): Unit = {
//1到 10 之间的数字
for (i <- 1 to 10) {
print(i+" ")
}
println()
for (c <- "Hello World") print(c + " ")
println()
//另外一种写法
for (i <- 1.to(10)) {
print(i+" ")
}
}
}
运行结果:
1 2 3 4 5 6 7 8 9 10
H e l l o W o r l d
1 2 3 4 5 6 7 8 9 10
4.2.2 while 循环
package com.wanlong
import scala.util.control.Breaks
object TestWhile {
/**
* 循环控制语句改变其正常的顺序执行。执行离开一个范围, 在该范围内创建的所有对象自动被销毁。但是Scala不支持break或continue语句。
*
* @param args
*/
def main(args: Array[String]) {
var i = 0;
while (i < 10) {
print(i+" ")
i += 1
}
println("------------------")
var j = 0;
val loop = new Breaks;
loop.breakable {
while (j < 10) {
print(j+" ")
if (j == 5) loop.break
j += 1
}
}
println("------------afper loop------")
}
}
运行结果:
0 1 2 3 4 5 6 7 8 9 ------------------
0 1 2 3 4 5 ------------afper loop------
4.2.3 if守卫
if守卫是Scala编程语言中的一种特性,它允许在for循环中使用if语句作为守卫(guard)条件,从而实现对循环迭代的过滤。
在Scala的for循环中,每个生成器(generator)后面都可以跟一个以if开头的boolean表达式,这个表达式就是if守卫。它用于指定一个条件,只有当该条件为真时,当前的迭代才会被执行。
例如,以下代码演示了如何使用if守卫来过滤出1到20之间的偶数并且大于2:
for( x <- 1 to 20 if x % 2 == 0 if x > 2 ) println(x)
if守卫特点:
- 多个守卫条件:一个for循环可以支持多个if守卫语句,它们之间以空格或回车字符分割。每个守卫条件都是一个boolean表达式,只有当所有守卫条件都为真时,当前的迭代才会被执行。
- 降低循环次数:通过if守卫,可以显著地降低循环次数,因为不满足条件的迭代会被直接过滤掉,从而提高程序的效率。
- 易读性:if守卫使得循环的过滤逻辑更加清晰和易读,因为过滤条件直接写在了循环语句中。
- 需要注意的是,if守卫并不支持其他循环语句,如while循环。在Scala中,while循环没有提供与for循环类似的守卫机制。但是,可以通过其他方式(如使用boolean类型变量或Breaks对象的break函数)来实现类似的循环控制。
复杂示例演示:
for {
i <- 1 to 3 // 第一个生成器,遍历1到3
j <- 1 to 2 // 第二个生成器,遍历1到2
if i != j // 第一个守卫条件,排除i和j相等的情况
if i + j > 3 // 第二个守卫条件,排除i和j之和不大于3的情况
} println(i,j) // 将满足条件的(i, j)输出
输出结果:
(3,1)
(3,2)
(3,1)
(3,2)
4.2.4 双重循环
//双重For循环 实现99乘法表
for(i<-1 to 9; j<-1 to i) {
print(i+"*"+j+"="+i*j+" ")
if(i==j) println()
}
运行结果:
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81
4.2.5 for循环绑定临时变量
//原始写法:
val names = Array("wanlong", "hadoop", " sqoop", " ")
for (name <- names if name.trim.nonEmpty) {
println(name.trim)
}
//优化:
for {
name <- names
tmpname = name.trim
if tmpname.nonEmpty
} {
println(tmpname)
}
运行结果:
wanlong
hadoop
sqoop
4.2.6 yield关键字
yield会把当前的元素记录下来,保存在集合中,当循环结束就会返回该集合。
val nlist = for (i <- 1 to 10) yield {
print(i+" ")
i * 2
}
println()
nlist.foreach(o=>print(o+" "))
//for循环中的yield会把当前的元素记录下来,保存在集合中,当循环结束就会返回该集合。
运行结果:
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
5. 函数
5.1 函数定义
Scala是一门既面向对象,又面向过程的语言。因此在Scala中有非常好的面向对象的特性,可以使用Scala来基于面向对象的思想开发大型复杂的系统和工程。
而且Scala也面向过程,因此Scala中有函数的概念。在Scala中,函数与类、对象等一样,都是一等公民。这意味着函数可以作为参数传递给其他函数、可以作为返回值从其他函数中返回,并且可以赋值给变量。Scala中的函数可以独立存在,不需要依赖任何类和对象。
Scala的函数式编程,就是Scala面向过程的最好的佐证。也正是因为函数式编程,才让Scala具备了Java所不具备的更强大的功能和特性。
5.2 第一个函数
package com.wanlong
object TestFunction {
//定义最大值函数
def max(x:Int,y:Int):Int ={
if(x>y){
x
}else{
y
}
}
//函数定义
def add(x: Int, y: Int): Int = {
x + y
}
}
在上面代码中定义了两个函数,其中max是最大值函数,在scala中,函数用def定义,def有点像java中的public static
这里要留意:
- 如果一个函数在定义的时候,没有输入参数,而且定义的时候使用的是空的参数列表,那么在调用的时候可以给(),也可以不给
- 如果没有参数,可以在定义的时候,不给定参数列表==》调用的时候不能给定(),只能f2
举例如下:
//如果一个函数在定义的时候,没有输入参数,而且定义的时候使用的是空的参数列表,那么在调用的时候可以给(),也可以不给
def f1() {
println("ss")
}
// 如果没有参数,可以在定义的时候,不给定参数列表==》调用的时候不能给定(),只能f2
def f2: Unit = {
println("hello f2")
}
def main(args: Array[String]) {
println(add(1, 2))
f1()
f1
f2
}
5.3 嵌套函数
//函数中定义函数
def function1(a: Int): Int = {
def function2(b: Int): Int = {
a + b
}
function2(2)
}
def main(args: Array[String]) {
//1+2
println(function1(1))
}
5.4 匿名函数
将函数传递给变量有三种传递方式:
- 将函数的值传递给变量
- 将函数直接传递给变量
- 匿名函数(没有写def,一般情况下就是将匿名函数赋值给变量使用)
//第一种方式
val num: Int = max(1, 2)
//第二种方式 注意 下划线前不加空格会报错
val num2=max _
println(num2(1,2))
//第三种方式
val num3 = (x: Int, y: Int) => if (x > y) x else y
注意:
- 函数可以赋值给变量也可以赋值给函数
5.5 给函数参数默认值
// scala 默认参数 Scala 可以为函数参数指定默认参数值,使用了默认参数,
// 你在调用函数的过程中可以不需要传递参数,这时函数就会调用它的默认参数值,如果传递了参数,则传递值会取代默认值。
def sayName(name: String = "wanlong") {
println("hello," + name)
};
sayName()
注意:一般情况将给定默认值的输入参数放到函数的参数列表最后,因为参数的补全从左到右
5.6 可变参数
在scala中,函数参数可变,但是只能是最后一个参数,*代表的是参数可重复
/**
* "在函数内部,变长参数的类型,实际为一数组,比如上例的string*类型实际为 Array[string]。
* 然而,如今你试图直接传入一个数组类型的参数给这个参数,
* 编译器会报错:\
* val arr* Array("hadoop", "hive", "hbase", "spark")
* printCourses(arr)
* "为了避免这种情况,你可以通过在变量后面添加_*来解决,这个符号告诉scala编译器在传递参数时逐个传入数组的每个元素,而不是数组整体。
* printCourse(arr: _*)
*
* @param args
*/
def printCourse(course: String*) {
for (c <- course) println(c)
}
def main(args: Array[String]) {
printCourse("hadoop", "spark", "hive")
printCourse("java")
val arr = Array("hadoop", "hive", "hbase", "spark")
// printCourse(arr)
printCourse(arr: _*)
}
注意: 在传入数组参数时,后面加_* ,告诉编译器,代表数组中元素一个个进行传递,而不是整个数组传递,传递整个数组是不认可的。
6. 高阶函数
在Scala中,高阶函数(Higher-Order Function)是指接受一个或多个函数作为参数,或者返回一个函数作为结果的函数。高阶函数极大地增强了Scala的表达能力,使得代码更加简洁和模块化
6.1 接受函数作为参数
def applyFunction(x: Int, f: Int => Int): Int = {
f(x)
}
// 定义一个简单的函数作为参数传递
val square: Int => Int = x => x * x
// 使用高阶函数applyFunction
val result = applyFunction(5, square)
println(result) // 输出: 25
6.2 返回函数作为结果
高阶函数也可以返回另一个函数作为结果。例如,我们可以定义一个函数makeIncrementer,它接受一个整数n并返回一个函数,该函数接受另一个整数并返回n加上该整数的结果:
def makeIncrementer(n: Int): Int => Int = {
(x: Int) => x + n
}
// 使用高阶函数makeIncrementer
val incrementBy5 = makeIncrementer(5)
val result = incrementBy5(10)
println(result) // 输出: 15
6.3 匿名函数
在Scala中,匿名函数(也称为Lambda表达式)常用于高阶函数。Lambda表达式的基本语法是:
(参数列表) => 表达式
例如,在上面的例子中,我们使用了Lambda表达式来定义square和makeIncrementer中的匿名函数。
6.4 高阶函数类型推断
高阶函数可以自动推断出参数类型,而不需要写明类型;而且对于只有一个参数的函数,还可以省去其小括号。
如果仅有的一个参数在右侧的函数体内只使用一次,则还可以将接收参数省略并且将参数用_来替代,诸如3 * _的这种语法,spark源码中大量使用了这种语法!
def triple(func:(Int)=>Int) = {
func(3)
}
triple((x:Int)=>3*x)
triple(x=>3*x)
triple(3*_)
6.5 常用高阶函数
/**
* map
* flatMap
* filter
* reduce
* sortBy
* groupBy
* countByKey
* take
* takeOrdered
* takeRight
* drop
* max
* min
* sum
* zip
* partition
* forall
* exists
*/
//map 对数组中的每个元素进行映射,返回处理后的元素
Array(1,2,3,4,5,6,7,8,9,10) map (_*2)
//foreach
Array(1,2,3,4,5,6,7,8,9,10) foreach println
//filter
Array(1,2,3,4,5,6,7,8,9,10) filter (_%2==0)
//reduceLeft 对数组中的元素进行左折叠操作,从左侧元素开始,进行reduce操作, 即先对元素1和元素2 进行处理,
//然后将第一次reduce的结果和元素3进行reduce,以此类推, reduce操作必须掌握!spark编程的重点!!!
// 最终返回reduce之后的结果 相当于1+2+3+4+5+6+7+8+9+10
Array(1,2,3,4,5,6,7,8,9,10) reduceLeft (_+_)
//flatmap 使用举例
(Array("hello","world") flatMap (_.toArray)).foreach(println)
其中,map和flatmap的操作需要重点关注。
map操作符主要用于将集合中的每个元素通过一个函数转换后,收集转换结果。例如,如果有一个字符串列表,map操作可以将每个字符串转换为大写字母,结果是一个包含大写字母的新列表。map操作保留了原始数据的结构,即每个元素仍然是独立的。
flatMap操作符则在此基础上增加了一个“摊平”的操作。它不仅对每个元素应用函数,还将函数的返回值扁平化。例如,如果有一个嵌套列表,flatMap会将内部的列表展开,并将所有元素合并到一个新的列表中。flatMap操作符特别适用于处理嵌套的数据结构,能够将多层嵌套的数据扁平化为单层结构。
举例如下:
有二箱鸡蛋,每箱5个,现在要把鸡蛋加工成煎蛋,然后分给学生。map做的事情:把二箱鸡蛋分别加工成煎蛋,还是放成原来的两箱,分给2组学生。flatMap做的事情:把二箱鸡蛋分别加工成煎蛋,然后放到一起【10个煎蛋】,分给10个学生
scala中map和flatmap的区别可以用一句话进行总结,原始RDD假如是10行,经过map操作后依旧是10行,但是flatmap后会>=10行,map是对每一行进行处理,flatmap对每一行进行处理后,如果处理结果的长度是1,那么就和map操作一样;如果处理后是个List或者Array类型,那么就会将这个List或者Array的每个元素变成1行。
以上,本文主要讲了scala基础用法,下一章,我会介绍scala中常用的集合,元组以及他们对应的api等内容。
最后,感谢阅读,如有错误,请不吝指正!!!