Scala 是一门 以 Java 虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言。
Spark 是新一代内存级大数据计算框架,是大数据的重要内容
Spark 是使用 Scala 编写的,为了更好的学习Spark,需要掌握 Scala
Spark 的兴起,带动 Scala 语言的发展。
函数是头等公民
OOP和函数式编程:Scala 是一门多范式的编程语言,Scala 支持面向对象和函数式编程。
Scala 源文件 .scala
会被编译成Java字节码文件 .class
,然后运行于JVM上,并且可以调用现有的Java类库,实现两种语言的无缝对接。
Scala语法简单:Scala单作为一门语言来看,非常的简洁高效。
Scala源于Java:Scala在设计时,参考Java的设计思想,也有自己独有的函数式编程语言的特点。
特点 | JAVA | SCALA |
---|---|---|
语法特点 | 相对Scala,代码冗余,但易读性高 | 相对于Java,语法简单,但是易读性差 |
声明变量 | 数据类型 变量名 = 值 | var 变量名 = 值 |
声明常量 | 使用final关键字修饰 | val 常量名 = 值 |
方法三要素 | 饰符 返回值类型 方法名(形参列表){方法体} | def 方法名(形参: 类型): 返回值类型={方法体} |
语法:[关键字] 变量名 [: 变量类型] = 变量值
共同点:两者都需要 初始化
,数据类型可以不指定,编译器会自动推导其类型。类型指定后,不能修改。
不同点:
val 是声明常量的关键字,声明的常量不能被再次赋值。
var 是声明变量的关键字,声明的变量可以被再次赋值。
Scala中一切数据皆对象,都是Any的子类,类似于Java中的Object。
Scala中的数据类型分为两大类:AnyVal(数值类型)、AnyRef(引用类型)
算术运算符:+(正号,加,字符串拼接)、-(负号,减)、*(乘)、/(除)、%(取余)
关系(比较)运算符:==(相等)、!=(不等)、<(小于)、>(大于)、<=(小于等于)、>=(大于等于)
逻辑运算符:&&(逻辑与)、||(逻辑或)、!(逻辑非)
赋值运算符:=(赋值运算符)、+=(相加后赋值)、-=(相减后赋值)、
*=(相乘后赋值)、/=(相除后赋值)、%=(取余后赋值)、
<<=(左移后赋值)、>>=(右移后赋值)、&=(按位与后赋值)
^=(按位异或后赋值)、|=(按位或后赋值)
位运算符:&(按位与运算符)、|(按位或运算符)、^(按位异或运算符)、
~(按位取反运算符)、<<(左移动运算符)、>>(右移动运算符)、
>>>(无符号右移)
组合几个表达式,使用 { }
包围起来,即代码块。
代码块中最后一个表达式的结果或一个值或者一个变量,即整个块的结果。
// 导入break所在的包
import scala.util.control.Breaks._
object Demo {
def main(args: Array[String]): Unit = {
var a = 5;
while(a<10){
println(a)
if(a==8){
println(a+"跳出循环")
break()
}
a+=1
}
}
}
为了完成某一功能的程序指令(语句)的集合,即函数
类中的函数称之为 方法
函数是带有参数的表达式
方法由 `def` 定义,def 后面跟着 方法名、形参列表、返回类型和方法体
方法不能作为单独的表达式而存在(参数为空的方法除外),函数可以。
方法名是方法调用,函数名代表函数对象本身。
在需要函数的地方,如果传递一个方法,会自动进行ETA(把方法转换为函数)展开。
传名参数本质上是个方法
可变参数:参数的个数可变,但是参数的数据类型必须是指定的类型
def test(s: String*): Unit = {println(s)}
test("Hello","World","Hello","Scala")
// ArraySeq(Hello, World, Hello, Scala)
def test(s:Any*): Unit = {println(s)}
test("Hello","World","Hello",2,2.5)
// ArraySeq(Hello, World, Hello, 2, 2.5)
如果参数列表中存在多个参数,那么可变参数一般放置在最后。
def test2( name : String, s: String* )
{
println(name + "," + s)
}
test2("汤姆","是个","男孩")
// 汤姆,ArraySeq(是个, 男孩)
参数可以设置默认值,在调用函数的时候,不给其传值,会使用默认值。
def test3(id: Int,name: String,gender: String ="男")=
{
println(id+"\t"+name+"\t"+gender)
}
test3(2,"李四")
// 2 李四 男
带名传参
def test4(id: Int,name: String,age: Int)={println(id+"\t"+name+"\t"+age)}
test4(name="张三",id=4,age=18)
// 4 张三 18
// 无参无返回值
def test{
println("无参无返回值")
}
test // 无参无返回值
// 无参有返回值
def test1: Int={
println("无参有返回值")
25
}
println(test1) // 无参有返回值 // 25
// 有参无返回值
def test2(name: String){
println(name+"有参无返回值")
}
test2("王五") // 王五有参无返回值
// 有参有返回值
def test3(name: String): String = {
println(name+"有参有返回值")
name
}
println(test3("张三")) // 张三有参有返回值 // 张三
// 多参无返回值
def test4(id: Int,name: String,age: Int)
{
println(id+"\t"+name+"\t"+age)
}
test4(5,"李四",25) // 5 李四 25
// 无参无返回值
val test = println("无参无返回值")
// 无参有返回值
val test1 => (Int) = 25
// 有参无返回值
val test2(a: Int) = println(a)
// 有参有返回值
val test3(a: Int,b: Int) => (Int) = a+b
// 多参无返回值
val test4(a: Int,b: Int)(c: Int) = (a+b)*c
函数内部引入外部变量,即闭包。
闭包,就是将一个函数和其相关的引用环境(变量)组合的一个整体(实体)
将一个接收多个参数的函数转化成一个接收一个参数的函数的过程,可以简单的理解为一种特殊的参数列表声明方式。
def test(id: Int,name: String,gender: String){}
def test(id: Int)(name: String)(gender: String){}
能省则省
- return可以省略,Scala会使用函数体的最后一行代码作为返回值
- 返回值类型如果能够推断出来,可以省略。
- 函数体只有一行代码,可以省略花括号。
- 函数无参,可以省略小括号,定义函数时省略小括号,调用函数时也需省略小括号。
- 如果函数明确声明Unit,那么即使函数体中使用return关键字也不起作用。
- 如果不关心名称,只关心逻辑处理,def也可以省略。
- 如果函数明确使用return关键字,那么函数返回时就不能使用自行推断了,需要声明返回值类型。
惰性函数:当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到首次访问,该函数才会执行。
def main(args: Array[String]): Unit = {
println("使用lazy关键字>>>")
lazy val res = sum(20,22)
println("————————")
println("res="+res)
println("没有使用lazy关键字>>>")
val res2 = sum(20,22)
println("————————")
println("res2="+res2)
}
def sum(n1: Int,n2: Int): Int = {
println("<<<<【sum函数被执行了】>>>>")
return n1 + n2
}
// console(控制台结果)
// 使用lazy关键字>>>
// ————————
// <<<<【sum函数被执行了】>>>>
// res=42
// 没有使用lazy关键字>>>
// <<<<【sum函数被执行了】>>>>
// ————————
// res2=42
Scala包的作用:
① 区分相同名字的类
② 当类很多时,可以很好地管理类
③ 控制访问范围
import java.lang._
import scala._
import scala.Predef._
scala中属性和方法的默认访问权限为 public,但scala中无public 关键字。
private 为私有权限,只在类的内部和伴生对象中可用。
protected 为受保护权限,Scala 中受保护权限比Java中更严格,同类、子类可以访问,同包无法访问。
private[包名]增加包访问权限,包名下的其他类也可以使用。
使用 @BeanProperty
注解的属性可以自动生成规范的 读写器方法(settter/getter)
val 修饰的对象不能改变对象的引用(即内存地址),可以改变对象属性的值。
var 修饰的对象可以修改对象的引用和修改对象的属性值
Scala 类的构造器包括:主构造器和辅助构造器
主构造器即定义类的语法
class className(参数列表)
{
}
辅助构造器,函数的名称为 this
可以有多个,编译器通过参数的个数来区分。
辅助构造方法不能直接构建对象,必须直接或者间接调用主构造器方法。
def this(参数列表){
// 调用主构造器
this(参数列表)
}
构造器参数
未用任何修饰、var 修饰、val 修饰
未用任何修饰符修饰,这个参数就是一个局部变量。
var 修饰参数,作为类的成员属性使用,可以修改
val 修饰参数,作为类只读属性使用,不能修改。
基本语法:class 子类名 extends 父类名{类体}
子类继承父类的属性和方法
scala 是单继承
//定义抽象类
abstract class Person{
}
//定义抽象属性,属性未初始化
val|var name: String
// 定义抽象方法,没有方法体
def hello(): String
class OuterClass{
val id:Int = 9527
val name = "周星星"
//内部类
class InnerClass{
val id:Int = 007
val name = "凌凌漆"
def getInfo = println( s"$id $name")
}
//外部类的方法 直接返回内部类对象
def getInnerClass: InnerClass={
val ic = new InnerClass
ic
}
}
/**
* 访问内部类
*/
//第一种:通过new外部类对象,内部类创建内部类对象
val innerClass: OuterClass.InnerClass = new OuterClass.InnerClass
// 使用内部类对象名调用内部类的方法
innserClass.getInfo
//第二种:先创建外部类,用外部类对象名调用返回内部类对象的方法
val outerClass = new OuterClass
val innerClass02: outerClass.InnerClass = outerClass.getInnerClass
innerClass02.getInfo
该对象是单例对象,若单例对象名与类名一致,则称该单例对象是这个类的伴生对象。
这个类的所有 " 静态 " 内容都可以放置在它的伴生对象中声明。
单例对象使用关键字 object
声明
单例对象对应的类称之为 伴生类,伴生对象的名称应该和伴生类名一致。
单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
同一个scala文件中,class的名字和object的名字相同,即class是object 的伴生类,object是class的伴生对象。
object Test{
def main(args: Array[String]): Unit = {
//(1)通过伴生对象的apply方法,实现不使用new关键字创建对象。
val p1 = Person()
println("p1.name=" + p1.name)
val p2 = Person("bobo")
println("p2.name=" + p2.name)
}
}
object Person {
def apply(): Person = {
println("apply空参被调用")
new Person("xx")
}
def apply(name: String): Person = {
println("apply有参被调用")
new Person(name)
}
}
Scala中,采用trait(特质)代替接口的概念。也就是说,多个类具有相同的特征时,就可以将这个特质独立出来,采用关键字 trait 声明。
Scala 中的 trait 中既可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。
Scala 引入 trait 特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了 extends 关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。
使用
当一个类去继承特质时,第一个连接词是 extends,后面是with
如果一个类在继承特质和父类时,应当把父类写在 extends 后。
特质可以同时拥有抽象方法和具体方法。
一个类可以混入(mixin)多个特质。
所有的Java 接口都可以当做 Scala 特质使用
类型检查:对象名.isInstanceOf[类名] 或者 对象名.getClass classof 类名
类型转换:对象名.asInstanceOf[类名]
type 别名 = 数据类型
var v:别名 = “abc”
1)第一种方式定义数组(定长数组)
定义:val arr1 = new ArrayInt
(1)new是关键字
(2)[Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定Any
(3)(10),表示数组的大小,确定后就不可以变化
定义数组
val arr1 = Array(1, 2)
(1)在定义数组时,直接赋值
(2)使用apply方法创建数组对象
1)定义变长数组
val arr01 = ArrayBuffer[Any](3, 2, 5)
(1)[Any]存放任意数据类型
(2)(3, 2, 5)初始化好的三个元素
(3)ArrayBuffer需要引入scala.collection.mutable.ArrayBuffer
ArrayBuffer是有序的集合
(2)增加元素使用的是append方法(),支持可变参数
1)说明
arr1.toBuffer //不可长数组转可变数组
arr2.toArray //可变数组转不可变数组
(1)arr2.toArray返回结果才是一个不可变数组+,arr2本身没有变化
(2)arr1.toBuffer返回结果才是一个可变数组,arr1本身没有变化
1)多维数组定义
val arr =Array.ofDimDouble
说明:二维数组中有三个一维数组,每个一维数组中有四个元素
1)说明
(1)List默认为不可变集合
(2)创建一个List(数据有顺序,可重复)
(3)遍历List
(4)List增加数据
(5)集合间合并:将一个整体拆成一个一个的个体,称为扁平化
(6)取指定数据
(7)空集合Nil
1)说明
(1)创建一个可变集合ListBuffer
(2)向集合中添加数据
(3)打印集合数据
Scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的Map是无序的。
1)说明
(1)创建不可变集合Map
(2)循环打印
(3)访问数据
(4)如果key不存在,返回0
1)说明
(1)创建可变集合
(2)打印集合
(3)向集合增加数据
(4)删除数据
(5)修改数据
默认情况下,Scala使用的是不可变集合,如果你想使用可变集合,需要引用scala.collection.mutable.Set包
1)说明
(1)Set默认是不可变集合,数据无序
(2)数据不可重复
(3)遍历集合
1)说明
(1)创建可变集合mutable.Set
(2)打印集合
(3)集合添加元素
(4)向集合中添加元素,返回一个新的Set
(5)删除数据
构造列表是有两个基本类型Nil和:: //Nill也可以表示一个空列表
Scala列表有三个基本操作 /* head返回列表第一个元素tail返回一个列表,包含除了第一个元素之外的其他元素isEmpty在列表为空时返回true
Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。
scala也提供了队列(Queue)的数据结构,队列的特点就是先进先出。进队和出队的方法分别为enqueue和dequeue。
案例类非常适合用于不可变的数据
不能使用new来创建对象,这是因为案例类有一个默认的apply方法来负责对象的创建。
使用==比较,可以直接比较堆和栈的上的值
当你创建包含参数的案例类时,这些参数是公开(public)的val,不能为案例类的属性重新赋值
拷贝:浅拷贝,只拷贝类的属性中(基本数据类型);深拷贝(不但拷贝基本数据类型还要拷贝引用数据类型)
密封类
(1)语法:
case classPerson (name: String, age: Int)
(2)说明
Scala中的模式匹配类似于Java中的switch语法,但是更加强大。
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句。
说明
(1)如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句,若没有case _分支,那么会抛出MatchError。
(2)每个case中,不用break语句,自动中断case。
(3)match case语句可以匹配任何类型,而不只是字面量。
(4)=>后面的代码块,是作为一个整体执行,可以使用{}括起来,也可以不括。
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为List[Int],而我们需要的是第一个元素是0的集合,这就是通过模式匹配实现的。
偏函数的功能能是返回输入的List集合的第二个元组
1)我们将可疑代码封装在try块中。在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止。
2)Scala的异常的工作机制和Java一样,但是Scala没有“checked(编译期)”异常,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。
3)异常捕捉的机制与其他语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在Scala中也不会报错,但这样是非常不好的编程风格。
4)finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样。
5)用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方
def test():Nothing = {
throw new Exception("不对")
}
6)Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。它向调用者函数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在Scala中,可以使用throws注释来声明异常