Scala基础
首先明确学习Scala的目的,是为了更好的学习Spark
对比Java代码对于reduce的实现,Scala代码就会非常简洁
什么是Scala
Scala是一门多范式的编程语言,它是一种类似Java的编程语言,它设计的初衷是为了实现可伸缩的语言 、并集成面向对象编程和函数式编程的各种特性
Scala基于Java虚拟机,也就是基于JVM的一门编程语言。所有Scala代码,都需要编译为字节码,然后交由Java虚拟机来运行
Scala和Java可以无缝相互操作,Scala可以任意调用Java代码,这个特性是非常好的
如何快速掌握Scala语言
多练!
Scala环境安装配置
下载Scala 2.12.11版本
https://www.scala-lang.org/download/all.html
windows下载 msi
mac os 下载 tgz
下载后解压并配置环境变量
SCALA_HOME=/Users/a/develop/scala-2.12.11
export PATH=SCALA_HOME/bin
scala后显示交互式对话框即配置成功
Scala命令行也称为Scala解释器(REPL),它会快速编译Scala代码为字节码,然后交给JVM来执行
这里的REPL表示:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环)
在Scala命令行内,输入Scala代码,解释器会直接返回结果
如果你没有指定变量来存放计算的值,那么值默认的名称会显示为res开头的变量,而且会显示结果的数据类型
scala的命令行也有自动补全功能,使用起来还是比较方便的
输入res,按键盘上的tab键,下面就会列出目前以res开头的变量名称
Scala中的变量分为两种:可变 var 和 不可变 val
可变var:可以随时修改var声明的变量的值
不可变val:val声明的变量,值不能被修改,否则会报错:error: reassignment to val
注意:在实际工作中,针对一些不需要改变值的变量,通常建议使用val,这样可以不用担心值被错误的修改(等于java中的final类型)。这样可以提高系统的稳定性和健壮性!
无论声明val变量,还是声明var变量,都可以手动指定变量的类型
如果不指定,Scala会自动根据值,进行类型推断
val c = 1 等价于 val c: Int = 1
数据类型
Scala中的数据类型可以分为两种,基本数据类型和增强版数据类型
基本数据类型有:Byte、Char、Short、Int、Long、Float、Double、Boolean
增强版数据类型有:StringOps、RichInt、RichDouble、RichChar等
scala使用这些增强版数据类给基本数据类型增加了上百种增强的功能
例如:RichInt提供的有一个to函数,1.to(10),此处Int会先隐式转换为RichInt,然后再调用其to函数
注意,to函数还可以这样写
使用基本数据类型,直接就可以调用RichInt中对应的函数
操作符
Scala的算术操作符与Java的算术操作符没有什么区别
比如+、-、*、/、%等,以及&、|、^、>>、<<等
注意:Scala中没有提供++、–操作符
我们只能使用+和-,比如count = 1,count++是错误的,必须写做count += 1
if 表达式
在Scala中,if表达式是有返回值的,就是if或者else中最后一行语句返回的值,这一点和java中的if是不一样的,java中的if表达式是没有返回值的
例如:val age = 20; if (age > 18) 1 else 0
在这因为if表达式是有返回值的,所以可以将if表达式赋予一个变量
注意:Scala会自动进行推断,取两个类型的公共父类型
例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int
例如,if(age > 18) “old” else 0,此时if和else的值分别是String和Int,则表达式的值是Any类型,Any是String和Int的公共父类型
如果if后面没有跟else,则默认else的值是Unit,也可以用()表示,类似于java中的void或者null
例如,val age = 12; if(age > 18) “old”。此时就相当于if(age > 18) “old” else ()。
此时表达式的值是Any
如果想在scala REPL中执行多行代码,该如何操作?
使用:paste和ctrl+D的方式
:paste 表示代码块的开始
ctrl+D 表示代码块的结束
高级for循环
最后来看一下高级for循环的用法
- if守卫
- if守卫模式,假设我们想要获取1~10之间的所有偶数,使用普通的for循环,需要把每一个数字都循环出来,然后判断是否是偶数
- 如果在for循环里面使用if守卫,可以在循环的时候就执行一定的逻辑,判断数值是否是偶数
这是if守卫模式的用法
- for推导式
- for推导式,一个典型例子是构造集合
- 我们在使用for循环迭代数字的时候,可以使用yield指定一个规则,对迭代出来的数字进行处理,并且创建一个新的集合
这是for的推导式用法
Scala的集合体系
集合的顶层接口是Iterable,Iterable接口下面还有一些子接口,Set、Seq、Map
这几个子接口下面有具体的实现类
- set下面有HashSet、LinkedHashSet、SortedSet等等
- seq下面有List、Buffer、Range等等
- Map下面有HashMap、SortedMap、LinkedHashMap等等
- 其中Buffer下面还有两个常用的,ArrayBuffer、ListBuffer
这是集合中一些常见的实现类
在讲这个集合体系的时候,还会关联讲到Array和Tuple这两个数据结构
集合
Scala中的集合是分成可变和不可变两类集合的
- 其中可变集合就是说,集合的元素可以动态修改
- 而不可变集合就是说,集合的元素在初始化之后,就无法修改了
可变集合:在scala.collection.mutable这个包下面
不可变集合:在scala.collection.immutable这个包下面
我们在创建集合的时候,如果不指定具体的包名,默认会使用不可变集合
Set
先来看一下Set,Set代表一个没有重复元素的集合
这个集合的特性和Java中Set集合的特性基本一样
Set集合分为可变的和不可变的集合,默认情况下使用的是不可变集合
Set可以直接使用,并且不需要使用new关键字
看一下Scala的文档,你会发现这个Set不仅仅是一个接口,它还是一个Object
注意:默认情况下直接创建的set集合是一个不可变集合,在这可以看到是在immutable包里面的,不可变集合中的元素一经初始化,就不能改变了,所以初始化后再向里面添加元素就报错了。
Set常用子类有:HashSet、LinkedHashSet、SortedSet
- HashSet:这个集合的特点是:集合中的元素不重复、无序
- LinkedHashSet:这个集合的特点是:集合中的元素不重复、有序,它会用一个链表维护插入顺序,可以保证集合中元素是有序的
- SortedSet:这个集合的特点是:集合中的元素不重复、有序,它会自动根据元素来进行排序
HashSet集合分为可变和不可变之分,immutable包下面的是不可变的,后期无法新增元素
在这里可以使用new关键字,也可以不使用,因为HashSet既是class,又是object,但是包名需要指定,否则无法识别
如果在创建集合的时候就初始化了元素,则可以省略泛型的定义,集合会自动识别元素的类型
- 再来看一下LinkedHashSet
- LinkedHashSet只有可变的,没有不可变的
- 最后来看一下SortedSet
SortedSet分为可变集合和不可变集合
List
接下来看一下List,List属于Seq接口的子接口
List代表一个不可变的列表
注意:为什么有的地方需要写类的全路径,而有的不需要呢?
由于immutable包是默认导入的,所以不需要导包,但是也会有个别虽然在immutable包下面的,但是不写全路径还是报错,原谅它把,反正你都带全路径肯定是没有问题的,后期我们会使用idea来开发,也不需要考虑包名的问题,不过在这为了演示起来更加清晰,就使用scala的命令行了。
针对List有head、tail以及::这几个操作
先演示一下head、tail操作
那其实head和tail就可以获取list中的所有元素了
通过::操作符,可以将head和tail的结果合并成一个List
:: 这种操作符要清楚,在spark源码中是有体现的,一定要能够看懂
针对List中的元素进行迭代和前面讲的Set集合的迭代是一样的
在这里List是不可变的列表,在实际工作中使用的时候会很不方便,因为我们很多场景下都是需要向列表中动态添加元素,这个时候该怎么办呢?
Scala还提供的有一个ListBuffer
ListBuffer:可以支持动态增加或者移除元素
ListBuffer也可以for循环迭代
Map
Map是一种可迭代的键值对(key/value)结构
Map分为可变和不可变,默认情况下使用的是不可变Map
创建一个不可变的Map
创建一个可变的Map
- 查询操作
- 获取指定key对应的value,如果key不存在,会报错
所以在实际工作中这样直接获取不太好,如果遇到了不存在的key程序会报错,导致程序异常退出。
那是不是可以考虑在获取key的值之前,先判断key是否存在
可以使用contains函数检查key是否存在、
使用if-else语句,如果指定的key不存在,则返回一个默认值
map中还有一个getOrElse函数
最后看一下Map的几个子类
HashMap、SortedMap和LinkedHashMap
- HashMap:是一个按照key的hash值进行排列存储的map
- SortedMap:可以自动对Map中的key进行排序【有序的map】
- LinkedHashMap:可以记住插入的key-value的顺序
HashMap分为可变和不可变的,没有什么特殊之处
在这主要演示一下SortedMap和LinkedHashMap
SortedMap是不可变的
Array
Scala中Array的含义与Java中的数组类似,长度不可变
由于Scala和Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上就是Java数组
数组初始化后,长度就固定下来了,而且元素全部根据其类型进行初始化
scala> val a = new ArrayInt
a: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
scala> a(0)
res65: Int = 0
scala> a(0)=1
scala> a(0)
res67: Int = 1
也可以直接使用Array()创建数组,元素类型自动推断
scala> val a = Array("hello", "world")
a: Array[String] = Array(hello, world)
scala> a(0)
res68: String = hello
scala> val a1 = Array("hello", 30)
a1: Array[Any] = Array(hello, 30)
如果想使用一个长度可变的数组,就需要使用到ArrayBuffer了
ArrayBuffer
Scala中ArrayBuffer与Java中的ArrayList类似,长度可变
ArrayBuffer:添加元素、移除元素
如果不想每次都使用全限定名,则可以预先导入ArrayBuffer类
scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer
- 初始化
使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer
注意:也支持直接创建并且初始化ArrayBuffer(1,2,3,4)
scala> val b = new ArrayBufferInt
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
代码块
- 添加元素
使用+=操作符,可以添加一个元素,或者多个元素
b += 1 或者 b += (2, 3, 4, 5)
scala> b += 1
res69: b.type = ArrayBuffer(1)
scala> b += (2, 3, 4, 5)
res70: b.type = ArrayBuffer(1, 2, 3, 4, 5)
使用insert()函数可以在指定位置插入元素,但是这种操作效率很低,因为需要移动指定位置后的所有元素
向3号角标的位置添加一个元素 30
scala> b.insert(3,30)
scala> b
res72: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 30, 4, 5)
- 移除元素
使用remove()函数可以移除指定位置的元素
移除1号角标的元素
scala> b.remove(1)
res73: Int = 2
scala> b
res74: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 3, 30, 4, 5)
注意:Array与ArrayBuffer可以互相进行转换
b.toArray:ArrayBuffer转Array
a.toBuffer:Array转ArrayBuffer
数组常见操作
下面看一下针对数据的常见操作
- 遍历Array和ArrayBuffer的两种方式
- 由于Array和ArrayBuffer都是有角标的,所以在迭代数组中元素的时候除了可以使用前面迭代集合的方式还可以使用角标迭代
scala> val b=ArrayBuffer(1, 2, 3, 4, 5)
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)
scala> for(i <- b) println(i)
1
2
3
4
5
scala> for(i <- 0 until b.length ) println(b(i))
1
2
3
4
5
- 求和、求最大值
scala> val a = Array(3, 2, 1, 4, 5)
a: Array[Int] = Array(3, 2, 1, 4, 5)
scala> val sum = a.sum
sum: Int = 15
scala> val max = a.max
max: Int = 5
Tuple
Tuple:称之为元组,它与Array类似,都是不可变的,但与数组不同的是元组可以包含不同类型的元素
Tuple中的元素角标从 1 开始
注意:目前 Scala 支持的元组最大长度为 22 ,对于更大长度可以使用集合或数组
演示一下
总结
前面讲了很多集合体系中的数据结构,有的是可变的,有的是不可变的,有的是既是可变的又是不可变的,听起来有点乱,在这里我们总结一下
可变集合:LinkedHashSet、ListBuffer、ArrayBuffer、LinkedHashMap
不可变集合:List、SortedMap
可变+不可变集合:Set、HashSet、SortedSet、Map、HashMap
还有两个编外人员:
Array、Tuple
Array:长度不可变,里面的元素可变
Tuple:长度不可变,里面的元素也不可变
Scala中函数的使用
前面我们学习了Scala集合体系中的一些数据结构,下面我们来学习一下Scala中函数的使用
先来看一下函数的定义
在Scala中定义函数需要使用 def 关键字,函数包括函数名、参数、函数体
Scala要求必须给出函数所有参数的类型,但是函数返回值的类型不是必须的,因为Scala可以自己根据函数体中的表达式推断出返回值类型。
函数中最后一行代码的返回值就是整个函数的返回值,不需要使用return,这一点与Java不同,java中函数的返回值是必须要使用return的
下面来实现一个单行函数和多行函数
- 单行函数
- 多行函数
函数的参数
- 默认参数
- 在Scala中,有时候我们调用某些函数时,不希望给出参数的具体值,而是希望使用参数自身默认的值,此时就需要在定义函数时使用默认参数。
- 如果给出的参数不够,则会从左往右依次应用参数。
java的写法:
scala写法:
特殊的函数-过程
在Scala中,定义函数时,如果函数体直接在花括号里面而没有使用=连接,则函数的返回值类型就是Unit,这样的函数称之为过程
过程通常用于不需要返回值的函数
过程还有一种写法,就是将函数的返回值类型显式定义为Unit
比较一下这四种写法的区别
前面两种写法的效果是一样的,都是函数
后面两种写法的效果是一样的,都是过程
lazy
Scala提供了lazy特性,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算
什么场景下需要使用lazy特性呢?
这种特性对于特别耗时的操作特别有用,比如打开文件这个操作。
即使D://test.txt文件不存在,代码也不会报错,只有变量使用时才会报错,这就是lazy这个特性。