Scala语言概述
Scala语言由Martin Odersky(马丁·奥德斯基)设计,其兼具面向对象和函数式编程的特性,和Java一样运行在Java虚拟机(JVM)中。Scala语言简洁,表达性强,其函数式编程的特性使其在大数据场景中得到广泛使用。
官方文档:Scala API Docs
【Scala语言特性】
- Scala程序运行在JVM上,和Java程序一样需经过编译、解释、运行的流程,对应的指令是scalac、scala
- Scala可以直接使用Java类库,并提供特有的类库
- 相比Java,Scala更为面向对象,同时兼具函数式编程的特性
- 语言特性简洁灵活,表达力强
HelloWorld案例
- 编写HelloWorld.scala文件
- 在命令行中编译scala文件为字节码文件
scala HelloWorld.scala
,在目录下会生成HelloWorld.class和HelloWorld$.class两个字节码文件 - 运行Scala程序
scala HelloWorld
- 引入scala库后可以使用java执行生成的字节码文件
java -cp %SCALA_HOME%/lib/scala-library.jar; HelloScala
object HelloWorld {
def main(args: Array[String]): Unit = {
//scala方法调用
println("Hello World")
//可使用Java中的方法
System.out.println("Hello World")
}
}
注释
Scala注释包括单行注释、多行注释、文档注释,与Java特性相同
变量&常量
变量var:对象引用可以改变var variable: String = "Hello Scala"
常量val:对象引用不可改变val value: Int = 0,相当于Java中用final修饰的变量
*Scala的变量/常量在定义时必须初始化,类型可以省略,由编译器自动推断类型
标识符
1可包含数字、字母、下划线、$,且首字符不能是数字
2只包含操作符,如+-*/!%
3标识符和Scala关键字相同时使用反引号包括,如`if`
权限修饰符
1default:默认是public,无需声明
2protected:仅能在子类中使用,与Java可以在同一包下和子类中使用不同
3private:仅可以在本类中使用
4private[包名]:增加在指定的包下的访问权限
字符串输出格式化
1使用+连接字符串:println("hello" + " world")
2使用*复制字符串:println("*" * 10)
3printf格式化输出:printf("name:%s; age:%d\n", "xiaoming", 18)
4模板字符串输出:println(s"name:${name}; age:${age}")
5原始字符串输出(仍能使用模板,但转义无效):print(raw"rowstring\t${name}")
6三引号字符串输出(在字符串跨多行时保留缩进格式等)
输入
1导入scala.io.StdIn类库,调用其中的方法readLine/readInt等读取键盘输入
2导入scala.io.Source类库,调用其中的方法Source.fromFile可以获取文件输入
方法传参不可变
1Scala遵循函数式编程理念,强调函数/方法不应该有副作用,因此不能对传入的参数进行修改
2方法传入的参数默认是val类型,在方法体内部不能改变传入的参数
Scala基本语法
数据类型
1Scala所有数据都是对象,都是Any的子类
2Any有两个子类:AnyVal数值类型、AnyRef引用类型
3数值类型都是AnyVal的子类,包括Char、Byte、Short、Int、Long、Float、Double、Boolean、StringOps、Unit
4StringOps是Scala对Java中String类的增强;Unit对应Java关键字void,表示无返回值,仅有一个实例()
5引用类型都是AnyRef的子类,特别的,Null是所有引用类型的子类,且Null类型仅有一个实例null
6Nothing是所有类型的子类,常用于发出终止信号
类型转换
1低精度到高精度会自动提升类型,高精度到低精度可调用方法强制转换,但会造成精度丢失
2(byte,short)和char之间不会相互自动转换
3byte,short,char三者可以计算,在计算时会首先转换为int类型
4数值和字符串之间的转换可以调用对象的方法
运算符
1算术运算符: + - * / %
2关系运算符: < > <= >= == !=
3逻辑运算符: && || !
4赋值运算符: += -= *= /= =
5位运算符: << >> >>> & | ~ ^异或
【注意】
●Scala中的==和equal方法都是比较值是否相等,而eq方法则是比较地址值
●Scala没有++自增、--自减、? : 三元运算符
Scala的运算符本质是对象的方法,在Scala中的方法调用有两种形式:
1对象.方法名(参数值,...)
2对象 方法名 (参数值,...) ,如果方法只有一个参数,括号可以省略
流程控制
块表达式
1由一对花括号{}包裹的一段代码就是块表达式
2块表达式有返回值,其返回值是{}中最后一个表达式的结果值
分支结构
Scala的分支控制仅有if分支判断结构,没有switch结构
if分支有三种主要的用法:
1单分支: if(...){...}
2双分支: if(...){...} else {...}
3多分支: if(...){...} else if(...) {...} .. else{...}
循环结构
for循环控制
范围遍历
循环守卫
在for循环中引入条件判断表达式,如果判断表达式为true进入循环体内部,否则跳过
嵌套循环
引入变量
yield获取for结构返回值
while循环控制
while和do-while循环结构使用和Java一样,但循环中断没有关键字break和continue,且返回类型是Unit。可以使用抛出异常并捕获的方式实现循环中断,在循环体外捕获可以实现break方式,在循环体内捕获则实现continue方式,Scala也提供了封装的Breaks.breakable方法
模式匹配
模式匹配语法使用match关键字声明,每个分支采用case关键字进行声明。匹配从第一个分支开始,如果匹配成功,执行对应的逻辑;如果匹配不成功,继续对下一个分支进行判断;如果所有case都不匹配,那么会执行case _默认分支。可以在模式匹配中增加条件守卫
模式匹配的使用对象
1匹配常量
2匹配类型
3匹配数组
4匹配列表
5匹配元组
6匹配对象/样例类
模式匹配的应用场景
1变量声明
2for推导式
3偏函数
【偏函数:对符合条件的输入参数进行处理,不符合条件的输入参数调用回调函数处理】
1orElse:用于多个偏函数的组合使用
2andThen:用于多个函数的连续调用
3applyorElse:符合条件调用偏函数,否则调用默认的回调函数
下划线的使用(luanru)
1用于类中的var属性变量的赋初值,使用默认值
2用于将方法转换为函数
3匿名函数化简,用下划线代替变量
4导入包下的所有内容
5导包时重命名为_,则表示丢弃/不导入该类或对象
6模式匹配中表示任意数据
函数式编程
1指令式编程:关注计算机的执行步骤,如C++、Java
2声明式编程:以数据结构形式表达程序执行的逻辑,如SQL
3函数式编程:关注数据之间的映射,如Scala、其它语言提供的lambda表示式和闭包
函数基本使用
1函数的定义
函数定义包括:关键字def,函数名,参数名,参数类型,函数返回值类型,函数体
●定义在class/object中的函数其实就是方法,可以重载
●定义在方法中的函数则不能在其作用域内声明同名不同参的其它函数,不可以重载
●方法保存在方法区中,函数本质是对象,保存在堆中
●函数调用底层是调用函数对象的apply方法,apply方法名可以省略
●方法可以手动转换为函数:方法名 _
2函数参数
【函数参数的多种形式】
1可变参数:参数个数不确定,置于参数列表最后
2参数默认值:指定参数默认值,一般置于参数列表后面
3带名参数:调用函数时指定参数名称
可变参数不能直接传递集合,如果需要将集合的元素传递给可变参数,可以通过数组名:_*传递
3函数定义简化
1return可以省略,Scala会使用函数体的最后一行代码作为返回值
2如果函数体只有一行代码,可以省略花括号
3返回值类型如果能够推断出来,那么可以省略(:和返回值类型一同省略)
4如果存在return,则不能省略返回值类型,必须指定
5如果函数明确声明Unit,那么即使函数体中使用return关键字也不起作用
6如果期望无返回值类型,可以省略等号,但等号和花括号不能同时省略
7如果函数无参但声明了参数列表,那么调用时小括号可加可不加
8如果函数没有声明参数列表,那么调用时小括号必须省略
9如果不关心名称,只关心逻辑处理,那么函数名(以及def关键字)可以省略
函数高级特性
1高阶函数
函数的使用除了定义和调用外,还有更高阶的用法:
1将函数作为值进行传递
2将函数作为参数进行传递
3将函数作为返回值进行传递
2匿名函数
匿名函数即定义时省略名称的函数,也称为lambda表达式。匿名函数可以按以下原则简化:
1参数类型可以省略,会根据形参自动推导
2类型省略后仅有一个参数,可以省略括号;没有参数或参数个数超过一个的不能省略
3匿名函数仅有一行,可以省略花括号
4参数只出现一次,且出现顺序和形参列表顺序一致的可以用_代替
5简化后仅剩下一个_,或者简化后的函数存在嵌套,则不能用下划线简化
3闭包Closure&柯里化Currying
闭包:一个函数和与其相关的引用环境(变量)组合的一个整体。当外层函数从栈内存里面释放了,内层函数可以通过打包保存的整体访问到外层函数的变量
柯里化:将函数的一个参数列表的多个参数,变成多个参数列表的过程,在只允许单一参数的框架中使用
4递归
函数在函数体内又调用了本身,称为递归调用
5控制抽象
1值调用:按值传递参数,计算值后再传递,多次调用的结果相同
2名调用:按名称传递参数,直接用实参替换函数中使用形参的地方,多次调用会产生不同的结果
6惰性加载
当函数返回值被声明为lazy时,函数的执行将被推迟,直到首次取值时该函数才会执行,注意lazy不能修饰var类型的变量
7利用函数递归、控制抽象实现的while循环
面向对象
包package
【Scala包管理方式】
1包名之间使用.分隔表示包的层级关系,如com.org.example
2通过嵌套的风格表示层级关系:一个源文件中可以声明多个包,父包访问子包需要导包,子包可以直接访问父包的内容
【导包方式】
1Java风格:文件首行使用import导入,文件中的所有类都可以使用
2局部导入:什么时候使用什么时候导入,在其作用范围内都可以使用
【包导入的通配符/限制等】
1import com.org.example._:导入所有成员
2import com.org.example.{A,B}:导入指定成员
3import com.org.example.{A=>AnotherName}:导入指定成员并重命名
4import com.org.example.{A=>_,_}:导入所有成员但屏蔽A
类Class
类的基本使用
1定义类:class 类名(){...}
2创建对象:new 类名(参数值,...)
3定义属性:[修饰符] val/var 属性名:类型 = 值,var修饰的属性可以使用_初始化
4定义方法:[修饰符] def 方法名(参数名:类型,...):返回值类型 = {方法体}
5封装:Scala为兼容Java API的使用提供了@BeanProperty注解,该注解能够自动生成属性的setter和getter方法,@BeanProperty注解不能用在private修饰的属性上。Scala的属性默认相当于public,但其底层实现为private,对外通过对象.属性的方式直接进行操作(底层为setter和getter方法),所以一般不将属性设置为private
6构造器:
●主构造器:定义在类名后面以()形式表示,语法为class 类名([修饰符] [val/var] 属性名:类型[=默认值],...),主构造器中val/var修饰的非private属性在class内部/外部都能使用,不用val/var修饰的变量是局部变量,只能在class内部使用
●辅助构造器:定义在class内部,语法为def this(参数名:类型,...){this(...);其它语句}
类的继承
1类的继承:class 子类名 extends 父类名 { 类体 }
2Scala继承和Java相同点:子类继承父类的属性和方法,且只能是单继承,构造器按父类到子类顺序调用
3Scala继承和Java不同点:Scala中属性和方法都是动态绑定,而Java中只有方法为动态绑定
抽象类
1定义抽象类:通过abstract关键字标记抽象类,其中可以包含抽象属性和抽象方法
2抽象属性:属性没有初始化,就是抽象属性
3抽象方法:只声明而没有实现的方法,就是抽象方法
抽象类使用的要点:
1父类为抽象类,子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
2重写非抽象方法和属性需要用override修饰,重写抽象方法和属性则可以不加override
3子类中调用父类的方法使用super关键字
4子类对非抽象属性重写,父类该非抽象属性只能是val 类型,因为var可变类型直接修改值即可,无需重写
伴生类&伴生对象
Scala是完全面向对象的语言,没有静态属性/方法的概念,但可以用单例对象实现与静态类似的功能。单例对象名和类名一致,则称该单例对象为类的伴生对象。类的所有“静态”内容都可以在它的伴生对象中声明
1伴生对象采用object关键字声明
2伴生对象对应的类称为伴生类,伴生对象和伴生类名称一致,且两者必须定义在同一个源文件中
3属性和方法可以通过伴生对象名直接调用访问
4使用new关键字构建对象时,调用类的构造方法;直接使用类名构建对象时,调用伴生对象的apply方法
5若伴生类主构造器()前加上private修饰符,则只能通过伴生对象的apply方法创建对象,因编译器底层对apply方法的支持,可以省略为类名(参数)的形式调用
6apply方法可以重载,对应调用类不同的构造器
特质
1Scala语言中的特质Trait相当于接口,Trait中可以有抽象属性和方法,也可以有具体的属性和方法
2一个类可以混入(mixin)多个特质,Trait是对类单继承机制的补充
3没有继承父类添加特质:class 类名 extends 特质 1 with 特质 2 with 特质 3 …
4继承父类同时混入特质:class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3…
5动态混入:创建对象时混入trait,无需在类声明时混入,提高类使用的灵活性
6依赖注入:在声明特质的首行添加 _: 依赖的特质/类 =>引入方法和属性
类型检查
集合
1Java集合类型:列表List、集合Set、映射Map;Scala集合类型:序列Seq,集合Set,映射Map
2不可变集合(scala.collection.immutable):集合长度不可修改,每次修改都会返回新对象而不会修改原对象
3可变集合(scala.collection.mutable):可以直接对原对象进行修改,而不会返回新的对象
可变集合
1可变数组ArrayBuffer
1通过apply方法创建:ArrayBuffer[元素类型](初始元素,...)
2使用new形式创建:new ArrayBuffer[元素类型]()
3获取指定角标元素:数组名(角标)
4修改指定角标元素: 数组名(角标) = 值
2可变列表ListBuffer
1通过apply方法创建:ListBuffer[元素类型](初始元素,...)
2使用new形式创建: new ListBuffer[元素类型]()
3获取指定角标元素: 集合名(角标)
4修改指定角标元素:集合名(角标) = 值
3可变集合mutable.Set
1通过apply方法创建:mutale.Set[元素类型](初始元素,...)
2可变集合特征:有序不可重复
4可变映射mutable.Map
1通过apply方法创建:mutable.Map[K的类型,V的类型](K->V,K->V,...)
2获取所有的键key:keyset、keys、keysIterator方法
3获取所有的值value:values、valuesIterator方法
4根据键获取值:getOrElse(key,默认值)存在返回对应值,不存在返回默认值
5修改指定键的值:集合名(key)=value
不可变集合
1不可变数组Array
1通过apply方法创建:Array[元素类型](初始元素,...)
2使用new形式创建:new Array[元素类型](数组的长度)
3获取指定角标元素:数组名(角标)
4修改指定角标元素:数组名(角标) = 值
2不可变列表List
1通过apply方法创建:List[元素类型](初始元素,...)
2特殊的添加元素方式::: 代表添加单个元素到集合最前面,类似+:,用::连接多个值时,最后一个::的右边必须是不可变的List或Nil,Nil是空列表
3特殊的添加元素方式::::代表添加指定不可变List集合中所有元素到集合最前面,类似++:
4获取指定角标元素:集合名(角标)
5修改指定角标元素:集合名.updated(角标,值)
3不可变集合Set
1通过apply方法创建:Set[元素类型](初始元素,...)
2不可变集合特征:无序不可重复
4不可变映射Map
1通过apply方法创建:Map[K的类型,V的类型]( K->V, K->V,...)
2获取所有的键key:keyset、keys、keysIterator方法
3获取所有的值value:values、valuesIterator方法
4根据键获取值:getOrElse(key,默认值)存在返回对应值,不存在返回默认值
5修改指定键的值:集合名.updated(key,value)
5元组tuple
1通过()方式创建:(初始元素,...)
2通过->方式创建(二元):K -> V
3元组一旦定义就不能修改、添加、删除元素
4元组最多只能存放22个元素
5元组获取元素:元组名._N
集合的通用操作
1添加:+、+:、:+、+=、+=:、++、++:、++=、++=:
2删除:-、-=、--、--=
3修改:update、updated
1带+与带-方法的区别:带+是添加元素,带-是删除元素
2一个与两个+/-的区别:一个+/-是添加/删除单个元素,两个+/-是添加/删除指定集合所有元素
3冒号在前后以及不带冒号的区别:冒号在前/不带冒号是将元素添加在集合最后面,冒号在后是将元素添加在集合最前面
4带=与不带=的区别:带=是在原集合中添加/删除元素,不带=是添加/删除元素生成新集合,原集合没有改变
5update与updated的区别:update是修改原集合的元素,updated是生成新的集合,原集合没有改变
集合操作demon
数组
列表
集合
映射
元组
队列
集合的函数
基本属性操作
1获取集合长度: length/size
2判断集合是否为空: isEmpty
3判断集合是否包含某个元素: contains
4将集合所有元素拼接成字符串: mkString(分隔符)
5将集合转成迭代器toIterator: 生成Iterator迭代器,一次性使用
6将集合转成迭代器toIterable: 生成Iterable迭代器,可重复使用
衍生集合
1去重: distinct
2删除前N个元素,保留剩余所有元素: drop(N)
3删除后N个元素,保留剩余所有元素: dropRight(N)
4获取前N个元素: take(N)
5获取后N个元素: takeRight(N)
6获取第一个元素: head
7获取最后一个元素: last
8获取除开第一个元素的所有元素: tail
9获取除开最后一个元素的所有元素: init
10反转: reverse
11获取指定角标范围的所有元素(不包含结束角标的元素): slice(开始角标,结束角标)
12滑窗: sliding(size,step=1)其中size为窗口大小,step为滑动长度
13交集: intersect
14差集: diff
15并集: union
16拉链: zip
17反拉链: unzip
初级计算函数
1获取最大值: max
根据指定字段获取最大元素: maxBy(func: 集合元素类型=> K),maxBy传入的函数是针对每个元素操作的,按照函数的返回值取集合中最大元素
2获取最小值: min
根据指定字段获取最小元素: minBy(func: 集合元素类型=> K),minBy传入的函数是针对每个元素操作的,按照函数的返回值取集合中最小元素
3求和: sum
4排序:sorted/sorBy/sortWith
●sorted: 根据集合元素直接排序(默认升序)
●sortBy(func: 集合元素类型 => K): 根据指定字段排序,sortBy传入的函数是针对每个元素操作的,根据函数的返回值对集合元素排序
●sortWith(lt: (集合元素类型,集合元素类型)=>Boolean): 根据规则排序,升序对应第一个参数 < 第二个参数;降序对应第一个参数 > 第二个参数
高级计算函数
1map(func: 集合元素类型=>B): 一对一映射,原集合每个元素计算得到新集合中的一个元素
●类似带有yield关键字的for循环,SQL中的select
●map传入的函数是针对每个元素操作的,元素有多少个,函数就会执行多少次
●map生成的集合元素个数 = 原集合元素个数
●map的使用场景: 一般用于数据类型/值的转换(一对一转换)
2foreach(func: 集合元素类型=>B):Unit :对集合元素遍历
●类似没有yield关键字的for循环
●foreach传入的函数是针对每个元素操作的,元素有多少个,函数就会执行多少次
3flatten:压平
●类似SQL中的explode
●flatten只针对集合嵌套集合的数据类型,用于将第二层集合元素放入第一层集合中保存
4flatMap(func: 集合元素类型=>集合 ) :即同时进行map和flatten操作,数据转换+压平
●flatMap传入的函数是针对每个元素操作的,元素有多少个,函数就会执行多少次
●flatMap生成的集合元素个数一般 >= 原集合元素个数
●flatMap的使用场景:一对多
5filter(func: 集合元素类型=>Boolean):按照指定条件过滤
●类似有守卫的for循环,SQL中的where
●filter传入的函数是针对每个元素操作的,元素有多少个,函数就会执行多少次
●最终保留函数返回值为true的数据
6groupBy(func: 集合元素类型=>K):按照指定字段分组
●类似SQL中的groupby
●groupBy传入的函数是针对每个元素操作的,并根据其返回值对元素分组
●groupBy生成的是Map,Key是函数的返回值,Value是一个装载key对应原集合所有元素的集合
7reduce(func: (集合元素类型,集合元素类型)=>集合元素类型):从左向右对集合所有元素聚合
●传入的函数第一个参数代表上一次聚合结果,第一次聚合时,初始值 = 集合第一个元素
●传入的函数第二个参数代表待聚合的元素
8reduceRight(func: (集合元素类型,集合元素类型)=>集合元素类型):从右向左对集合所有元素聚合
●传入的函数第二个参数代表上一次聚合结果,第一次聚合时,初始值 = 集合最后一个元素
●传入的函数第一个参数代表待聚合的元素
9fold(默认值)(func: (集合元素类型,集合元素类型)=>集合元素类型):从左向右对集合所有元素聚合
●传入的函数第一个参数代表上一次聚合结果,第一次聚合时,初始值 = 默认值
●传入的函数第二个参数代表待聚合的元素
10foldRight(默认值)(func: (集合元素类型,集合元素类型)=>集合元素类型):从右向左对所有元素聚合
●传入的函数第二个参数代表上一次聚合结果,第一次聚合时,初始值 = 默认值
●传入的函数第一个参数代表待聚合的元素
集合函数应用demon
1merge合并映射:同key元素累加value
2worldcount词频计数
异常处理
1Scala没有编译异常这个概念,异常都是在运行的时候捕获处理
2所有异常都是Throwable的子类型,throw表达式类型是Nothing
3可以使用try{...}catch{...}finally{...}捕获处理异常
4在获取某个表达式的值时可以使用scala.util.Try
异常处理
Scala复制代码
//try{...}catch{...}finally{...}
val list = List("1\tzhagnsan\t20\tbeijing", "2\t\t30\tshenzhen", "3\twangwu\t\tbeijing")
list.map(x => {
val splitlist = x.split("\t")
val area = splitlist(3)
val age = try {
splitlist(2).toInt
} catch {
case ex: Exception => 0 //数据处理中的异常捕获处理
}
(area, age)
}).foreach(println)
//Try
list.map(x => {
val splitlist = x.split("\t")
val area = splitlist(3)
val age = Try(splitlist(2).toInt).getOrElse(0) //Try获取表达式值,再用getorElse取出其中的值
(area, age)
}).foreach(println)
隐式转换
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译,拓展类的方法
隐式函数
Scala复制代码
object implicittest {
def main(args: Array[String]): Unit = {
//类的方式导入隐式函数
val imutil = new implicitutil1
import imutil.double2int
//伴生对象导入隐式函数
import implicitutil2.string2int
val num1: Int = 0.1 //通过隐式函数自动转换
val num2: Int = "String" //通过隐式函数自动转换
println(num1)
println(num2)
}
}
class implicitutil1 {
//将浮点转换为整型的隐式函数
implicit def double2int(x: Double): Int = {
x.toInt
}
}
object implicitutil2 {
//将字符串转换为整型的隐式函数
implicit def string2int(x: String): Int = {
x.length
}
}