一、介绍
Scala(Scalable Language)是一门多范式的编程语言
Scala是一种纯面向对象语言,每个值都是对象
Scala也是一种函数式语言,其函数也能当成值来使用,Scala的case class及其内置的模式匹配相当于函数式编程语言中常用的代数类型
Scala是Spark的开发语言,而Spark是目前非常主流的大数据框架
二、环境配置
Scala的安装非常的简单,你只需要一款IDEAL软件即可
2.1 安装Scala插件
编辑
2.2 安装Scala SDK
编辑
编辑
三、基础语法
3.1 基础用法
- 大小写敏感
- 所有类名的第一个字母需要大写如MyHomeClass
- 所有方法的第一个字母小写如myMethodAdd
- main函数入口 def main(args: Array[String])
- 标志符可以使用_、$、字母开头,后面可以使用数字或者字母
- 注释符号 //
- 每行的结束可以有选择性的使用;但是当一行中有多个语句的时候必须要使用;来进行分隔
3.2 关键字
abstract | case | catch | class |
def | do | else | extends |
false | final | finally | for |
forSome | if | implicit | import |
lazy | match | new | null |
object | override | package | private |
protected | return | sealed | super |
this | throw | trait | try |
true | type | val | var |
while | with | yield | |
- | : | = | => |
<- | <: | <% | >: |
# | @ |
sealed:翻译密封的,指不能在类定义的文件之外定义任何新的子类
implicit:翻译隐式的,即隐式转换中变量的标记
trait:翻译特性,Scala中没有Interface,而是使用trait接口
3.3 包
Sclala使用package关键字来定义包,使用import关键字来导入包
包的定义
// 第一种方式类似Java package com.mp class HelloWorld // 第二种方式类似C# package com.mp{ class HelloWorld }
包的导入
import java.mp._ // 引入包内所有成员 import java.mp.Hello // 引如包中的Hello成员 import java.mp.{Hello,Bye}// 引入包中的Hello和Bye成员
3.3 数据类型
和Java一样一共有7种数据类型,Byte、Char、Short、Int、Long、Float、Double、Boolean和Unit
注意:Unit表示无值,和其他语言中的void等同。返回值为()
使用val定义的变量,其值是不能进行修改的
val address = "CSDN"
使用var定义的变量,其值是可以进行修改的
var age = 18
定义带有数据类型的变量
val address:String = "CSDN"
定义函数型变量
var s:Unit=println("Hello") print(s) >>> Hello () var s:Unit=println("Hello") s >>> Hello
3.4 输出
f 标志符可进行格式化的输出
s 标志符可插入任何表达式的输出
var name="Iphone20" var price=9999.999 var business="Apple" println("公司"+business+"产品"+name) // 字符串通过+进行相连 println(s"${math.sqrt(2)}") println(f"商品:$name%s,价格:$price%.1f") // 带换行 printf("商品 %s 价格 %.1f",name,price) // 不带换行
3.5 结构控制语句
if
var a=10 // if语句 var if_demo1=if(a>8) "Success" else "False" println(if_demo1) var if_demo2={ if (a<=2) "D" else if (a >= 2 && a<=5 ) "C" else if (a>=6 && a<=8) "B" else "A" } println(if_demo2) >>> Success A
for
// for循环 val arr = Array(1, 2, 3, 4, 5) // 全部遍历 for (i <- arr) { println(i) } // 带下标 for (i <- 0 to 4) { print(arr(i) + " ") }; println() for (i <- 0 until 5) { print(arr(i) + " ") }; println() // 条件 for (i <- 0 until 5; if i % 2 == 0) { print(arr(i) + " ") }; println() // 双重循环 for (i <- 0 to 2; j <- 0 to 2; if i != j) { print("good") }; println() //yield的用法 var b=for (i<-1 to 15 if i%3==0) yield i println(b) >>> 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 3 5 goodgoodgoodgoodgoodgood Vector(3, 6, 9, 12, 15)
3.6 方法定义
- 方法体中的最后一行就是返回值
- 一般将默认参数置后
- 崇尚至简原则,return省略掉
- 若函数体只有一行,花括号可省
- 若返回值是字符串或者整数等可推断,则返回值类型可省
- 含return则不可省返回值类型
- 若返回值类型为Unit,则函数返回了其他类型也会无效
- 若期望是无返回值类型,则可省略等号如def f(){printlen("hello")}
- 若函数无参,声明了参数列表如f(),则调用的时小括号加不加均可
- 若函数无参,没有声明参数列表如f,则调用的时候小括号一定不能加
// 无参无返回值 def f1(): Unit = { println("f1:无参无返回值") } f1() // 无参有返回值 def f2(): String = { return "f2:无参有返回值" } println(f2()) // 有参无返回值 def f3(s: String): Unit = { println(s) } f3("f3:有参无返回值") // 有参有返回值 def f4(s: String): String = { s } println(f4("f4:有参有返回值")) // 可变参数 def f5(a: String*): Unit = { for (i <- a) { print(i) } } f5("f5:", "可变参数"); println() // 默认参数 def f6(a: Int = 1, b: Int = 2): Int = { a + b } println(f6()) >>> f1:无参无返回值 f2:无参有返回值 f3:有参无返回值 f4:有参有返回值 f5:可变参数 3
3.7 隐式转换函数/类
隐式转换函数是以implicit关键词声明的带有单个参数的函数, 该函数会自动应用,主要用于将值从一种类型转换为另一种类型
// 将Float和Double自动转化为Int implicit def f1(d: Double): Int = { d.toInt } implicit def f2(f: Float): Int = { f.toInt } val num1: Int = 3.3 val num2: Int = 3.2f println("num1+num2: " + (num1 + num2)) >>> num1+num2: 6
在使用类转换的时候如A转B,A类并非是完全转换成B,A会保留A的特点,因此可以将隐式转换看成类的继承
object Main { def main(args: Array[String]): Unit = { // 将Float和Double自动转化为Int implicit def AinheritB(a: A): B = { new B } val a = new A a.fuc1() a.fuc2() } } class A { def fuc1(): Unit = { println("This is A") } } class B { def fuc2(): Unit = { println("This is B") } } >>> This is A This is B
在Scala2.10之后提供了隐式类,使用implicit来标记类
注意点
1 隐式类的构造函数有且只能有一个
2 隐式类不能是顶级的,只能在包对象、类对象、伴生对象里
3 隐式类不能是Case Class
4 作用域内不能有与之同名的标志符
3.8 匿名函数
没有名字的函数就是匿名函数,不关心名称,只关注逻辑处理。其实就是lambda函数
形式 (参数) => {函数体}
如 val y = (x:Double) => x*x
1 (x:Double) => x*x 为匿名函数
2 y为指向匿名函数的变量
// 实现输入两个整数,返回两个整数之积的隐式函数 val y=(n1:Int,n2:Int)=>{ println("隐式函数!") n1*n2 } println("隐式函数的类型是:"+y) println(y(2,3))、 >>> 隐式函数的类型是:Main$$$Lambda$16/0x0000000800c03000@3cbbc1e0 隐式函数! 6
3.9 参数函数
一个函数可以是另外一个函数的参数,这里的函数形式必须为函数名:(参数类型) => 返回值类型
// 函数作为参数 def f(f: (Int) => Int, x: Int, y: Int): Int = { f(x) + y } // 定义作为参数的函数 def fun(x: Int): Int = { x * x } // 输出 println(f(fun, 7, 8)) //第二种方式,对于逻辑简单的函数直接写在函数参数位置 println(f((x: Int) => { x * x }, 7, 8)) >>> 57 57
3.10 嵌套函数
def fun1(x: Int) = { def fun2(y: Int) = { println("y=" + x * y) } fun2 _ } fun1(3)(4)
3.11 val函数
val f1=(a:Int,b:Int)=>a+b val f2:(Int,Int)=>Int=(x,y)=>x+y val f3=(x:Int)=>println(x) val f4:(Int)=>Unit=(x)=>println(x) println(f1(1,2)) println(f2(1,2)) f3(3) f4(4)
3.12 传值调用与传名调用
Scala中函数A的参数可以是一个函数B。其实逻辑上是函数B会返回一个值,该值再放入函数A中。注意这里的传名是f1(f: =>Long)而传函数是f1(f:Long =>Long),二者是不一样的,传名的内容是一串代码
def currentTime():Long={ System.nanoTime() } def f1(f: =>Long):Unit={ println("Time"+f) } def f2(time:Long)={ println("Time"+time) } f1(currentTime) val time=currentTime f2(time)
3.13 科里化函数
科里化是将原来接受两个参数的函数变成新的接受一个参数的函数过程。直观的说就是将一个有两个参数的函数分割成两个只有一个参数的函数
def substract1(x:Int)(y:Int)=x-y // substract2 返回的是一个匿名函数:(y:Int)=>x-y def substract2(x:Int)=(y:Int)=>x-y var result1=substract1(5)_ var result2=substract2(5)(_) var result3=substract2(5) var s1=result1(2) var s2=result2(2) var s3=result3(2) println(result1) println(result2) println(result3) println(s1) println(s2) println(s3) >>> Main$$$Lambda$16/0x0000000800c021f8@50f8360d Main$$$Lambda$17/0x0000000800c025e8@2cb4c3ab 3 3
3.14 闭包
一个函数与其引用的变量组合成的一个整体叫做闭包
def substract(x:Int)=(y:Int)=>x-y val f = substract(20) println(f(1)) println(f(2))
f是一个闭包,因为它有函数,有变量。而y只是属于它返回的匿名函数中的内容,因此y虽然不同,但并不影响f是个闭包
问题1:编写一个函数,接受两个整数,返回它们的乘积。分别使用常规、科里化、闭包方式来完成
def add1(x: Int, y: Int) = x * y def add2(x: Int)(y: Int) = x * y def add3(x: Int) = (y: Int) => x * y
问题2:使用科里化函数和隐式类实现忽略大小写进行字符串比较
object Main { def eq1(s1: String, s2: String): Boolean = { s1.equals(s2) } def main(args: Array[String]): Unit = { implicit class ImCompare(s1: String) { def check(s2: String)(f: (String, String) => Boolean): Boolean = { f(s1.toLowerCase, s2.toLowerCase) } } val s1 = "HELLO" val s2 = "hello" println(s1.check(s2)(eq1)) } }
3.15 偏函数
表现形式为def f:partialFunction[A,B],其中A输入数据类型,B为输出数据类型。一般来说,只有在模式匹配中才可以看的见它
// 常规写法 def f1(s: String): Int = { if (s.equals("a")) 1 else 2 } // 偏函数 def f2: PartialFunction[String, Int] = { case "a" => 1 case _ => 2 }
3.15 _ 常见用法
1 方法转函数
def m1(x:Int,y:Int)=x+y val f1=m1 _
2 集合的每个元素
val list=List(1,2,3,4) val list1=list.map(_* 10)
3 获取元组Tuple中的元素
val t=("hadoop",3.14,100) t._1 t._2 t._3
4 模式匹配
val word="hadoop" val result=word math{ case "hadoop" => 1 case "spark" => 2 case _ => 0//以上都没有被匹配到才会被执行,相当于java中的default }
5 队列
val list=List(1,2,3,4) list match{ case List(_,_*) => 1 case _ => 2 }
6 导入包的时候
//通配符,类似Java中的 import java.util.* import scala.collection.mutable._ //表示引入的时候将scala.collection.mutable包下面所有的类都导入 import java.util.{Date =>_,_} //在java.util包下将Date屏蔽
7、初始化变量
var name:String=_ //在这里,name也可以声明为null,例:var name:String=null。这里的下划线和null的作用是一样的。 var age:Int=_ //在这里,age也可以声明为0,例:var age:Int=0。这里的下划线和0的作用是一样的。
8 函数中使用
val set=setFunction(3.0,_:Double) println(set(7.1)) //Scala中特有的“偏函数”用法
9 传参
val result=sum(1 to 5: _*) //当函数接收的参数不定长的时候,假如你想输入一个队列,可以在一个队列后加入“:_*”,因此,这里的“1 to 5”也可以改写为:“Seq(1,2,3,4,5)”。 printArgs(List("a", "b"): _*) //将集合中的元素传给printArgs方法
10 类型通配符
def printList(list: List[_]): Unit ={ list.foreach(elem => println(elem + " ")) }//打印出所有List类型元素
11 将函数赋给变量
//如果尝试将函数直接赋值给一个变量,这个函数会被直接调用,并将调用的结果赋值给变量,如果在函数名称后面加上_,那么赋值的是函数体本身 class Test { def fun = { // Some code } val funLike = fun _ }
12 参数展开
def getConnectionProps = { ( Config.getHost, Config.getPort, Config.getSommElse, Config.getSommElsePartTwo ) }
13 简化函数
val nums = List(1,2,3,4,5,6,7,8,9,10) nums filter (_ % 2 == 0) nums reduce (_ + _) nums.exists(_ > 5) nums.takeWhile(_ < 8)
14 获取某数据类型的默认值
var i:Int=_
3.15 运算符
符号 | 作用 |
=> | 定义函数,函数变量 => 返回值 |
<- | 循环中将变赋给索引 for(i <- arr ) |
-> | 所有对象都有该方法,a->b返回一个二元组对象(a,b) |
_N | 访问元组的第N个元素 |
3.16 Nil、Null、None、Nothing的区别
名称 | 作用 |
Nil | 空的List |
Null | AnyRe的子类,null是Null的唯一对象 |
None | Option的子类,若Option没有值则返回None |
Nothing | 所有类型的子类,没有对象,但是可以定义类型,如果一个类型抛出异常,这个返回值就是Nothing |
四、进阶
4.1 集合
形式 | 内容 |
1 长度固定,内容可变 | Array |
2 长度可变的数组 | ArrayBuffer |
3 长度可变的列表 | ListBuffer |
4 不可变列表 | List |
5 队列 | Queue |
4.1.1 Array
注意:取值是用()而不是[]
定义与赋值
// 定义不可变数组 var arr:Array[Int]=new Array[Int](5) // Int类型自动赋值为0 println(arr(1)) // 定义数组后可指定数组的位置进行赋值 arr.update(1,30) println(arr(1)) // 添加元素
循环遍历
// 定义不可变数组 var arr: Array[Int] = new Array[Int](5) var arr1: Array[Int] = new Array(1,2,3,4,5) // 方法一: 普通for循环 for (i <- 0 until arr.length) { print(arr(i)) } println() // 方法二: 简化for循环 for (elem: Int <- arr) { print(elem) } println() // 方法三: foreach arr.foreach(print); println() arr.foreach(print(_)); println() // 方法四: 迭代器 var it: Iterator[Int] = arr.iterator while (it.hasNext) { print(it.next()) }
集合中的运算符
运算符的本质:方法
若运算符包含冒号,且冒号在后,则运算顺序为从右到左
val a = List(1,2,3) val b = List(4,5,6)
符号 | 操作 | 结果 | 位置 | 解释 |
:: | a::b | List(List(1,2,3),4,5,6) | 前插 | 将a当做一个元素插入到b前面 |
+: | a+:b | List(List(1,2,3),4,5,6) | 前插 | 同上 |
:+ | a:+b | List(1,2,3,List(4,5,,6)) | 后插 | 把b当做一个元素插入到a后面 |
++ | a++b | List(1,2,3,4,5,6) | 拼接 | a和b集合顺序合并 |
++: | a++:b | List(1,2,3,4,5,6) | 拼接 | 同上 |
::: | a:::b | List(1,2,3,4,5,6) | 拼接 | 同上 |
注意:arr+:30和30:+arr都是错误,集合不能插在数字前也不能插在数字后
4.1.2 ArrayBuffer
var arr: ArrayBuffer[Int] = ArrayBuffer(1, 3, 5) // 添加元素,不可变的用符号,可变的用方法 arr+=9 arr.append(1) var arr1: ArrayBuffer[Int] = arr.+:(20) // 删除元素,双参数表示删除这个区间的数据 arr.remove(1) arr.remove(1,2) // 修改元素 arr.update(1,30) // 插入元素 arr.insert(1,20)
4.1.3 Array与ArrayBuffer的互相转换
var arr: ArrayBuffer[Int] = ArrayBuffer(1, 3, 5) // 可变 ==> 不可变 var arr1:Array[Int] = arr.toArray // 不可变 ==> 可变 val arr2:mutable.Buffer[Int] = arr1.toBuffer
4.1.4 ListBuffer
var lb = ListBuffer(1,2,3) lb+=(6,7,8)
规律:所有可变长的数据类型(带Buffer)添加元素可以直接使用+
4.1.5 Queue
队列是一个有序列表,遵循先入先出的原则。在 scala 中, 有 scala.collection.mutable.Queue 和 scala.collection.immutable.Queue , 一般来说,我们在开发中通常使用可变集合中的队列即scala.collection.mutable.Queue
队列创建
// 创建队列 val q = new mutable.Queue[Any]
方法 | 作用 | 案例 |
+ | 添加元素 | q+=5 |
enqueue | 入队尾 | q.enqueue(100,100) |
+ | 添加列表元素 | q+=List(1,2,3) |
++ | 将列表元素一个一个添加 | q++=List(1,2,3) |
dequeue | 出队头 | q.dequeue() |
head | 获取第一个元素 | q.head |
last | 获取最后一个元素 | q.last |
tail | 获取初第一个元素之外的所有元素. 可以级联使用 |
q.tail q.tail.tail.tail |
4.2 运算符重载
def main(args: Array[String]): Unit = { val s=new Student s+10 s+10 s.+(10) println(s.age) } class Student{ var age=0 def +(n:Int):Unit={ this.age+=n } } >>> 30
4.3 二维数组
// 定义 val arr = Array.ofDim[Int](3, 4)
4.4 List
// 定义列表 // 方法一: 普通方法 val ls1 = List(1,2,3,4) // 方法二: Nil代表空的List val ls2=1::2::3::4::5::Nil
val l = List(1,2,3)