概述
Scala (Scalable Language, 可伸缩语言) , 从计算机的角度来讲, Scala是一门完整的可伸缩的完全面向对象的软件编程语言。之所以说它可伸缩, 是因为这门语言体现了面向对象, 函数式编程等多种不同的语言范式, 且融合了不同语言新的特性, 同时它也是一门强类型和静态类型的语言。在大数据领域中, 其开发效率更高, 更直观, 更容易理解。
Scala编程语言是由联邦理工学院洛桑(EPFL)的Martin Odersky于2003年设计研发的.
官网 : https://www.scala-lang.org/
HelloWord
package cn.lofe.scala
Object HelloWord {
def main(args: Array[String]): Unit = {
System.out.println("Hello Scala")
println("Hello Scala")
}
}
由于scala语言是基于Java语言开发的,所以也会编译为class文件,那么我们可以通过反编译指令javapjavap -c -l 类名
或反编译工具jd-gui.exe来查看scala编译后的代码。
变量和数据类型
注释
Scala注释使用和Java完全一样。
// 单行注释
/*
多行注释
*/
/**
文档注释
*/
变量
变量是一种使用方便的占位符,用于引用计算机内存地址,变量创建后会占用一定的内存空间。基于变量的数据类型,操作系统会进行内存分配并且决定什么将被储存在保留内存中。因此,通过给变量分配不同的数据类型,可以在这些变量中存储整数,小数或者字母。
声明格式
var/val 变量名 : 变量类型 = 变量值
object ScalaVariable {
def main(args: Array[String]): Unit = {
// 用户名称
var username : String = "zhangsan"
// 用户密码
val userpswd : String = "000000"
}
}
如果变量的类型能够通过变量值推断出来, name可以省略类型声明, 省略后, 由Scala编译器在编译时自动声明。
object ScalaVariable {
def main(args: Array[String]): Unit = {
var username = "zhangsan"
val userpswd = "000000"
}
}
初始化
要求 : 必须显示初始化
object ScalaVariable {
def main(args: Array[String]): Unit = {
var username // Error
val username = "zhangsan" // Nice
}
}
可变变量
值可以改变的变量, 称之为可变变量, 但是变量类型无法发生改变。Scala中可变变量使用关键字var声明
object ScalaVariable {
def main(args: Array[String]): Unit = {
var username : String = "zhangsan"
}
}
不可变变量
值一旦初始化后无法改变的变量, 称之为不可变变量。Scala中不可变变量使用关键字val声明, 类似于Java语言中的final关键字。
object ScalaVariable {
def main(args: Array[String]): Unit = {
val username : String = "zhangsan"
}
}
注意 : Java中的字符串(String), 称之为不可变字符串。
标识符
> Scala可以使用两种形式的标识符, 字符数字和符号。
> 使用符号命名时, 可能与代码冲突的字符一般不能使用。
> 当Scala代码编译为字节码文件时, 其内部实现时编译器会使用转义的标识符, 比如 :-> 使用 $colon$minus$greater 来表示这个符号。
> Scala的命名规范采用和Java类似的 “驼峰” 式的命名规范, 且避免使用 “$” 开始和使用 “_” 结尾的标识符, 防止冲突。
> 如果想使用关键字来命名变量时, 可使用转义字符(飘号)进行转义, 其内部实现仍然使用转义来表示该关键字。比如 `private` 。
字符串
在Scala中, 字符串的类型实际上就是Java中的String类, 它本身是没有String类的。
> 字符串连接 :
val name : String = "zhang san"
println("name = " + name)//name = zhang san
> 传值字符串
printf("name = %s", name)//name = zhang san
> 插值字符串
println(s"name = $name")//name = zhang san
println(s"name = ${name.substring(0,1)} // name = z
> 多行字符串
println(
s""""
|name = $name
|sex = $sex
"""".stripMargin)
//name = zhang san
//sex = Man
输入输出
> 输入
从控制台中获取输入
val age : Int = StdIn.readInt()
从文件中获取输入
//使用相对路径时, 是相对于当前Project目录 ; 也可使用绝对路径
val strings = Source.fromFile("input/user.json").getLines()
for (elem <- strings) {
println(elem)
}
// OR
Source.fromFile("input/user.json").foreach(
elem => {
println(elem)
}
)
> 输出到文件中 : 底层用的是 java 中的 I/O 类
//使用相对路径时, 是相对于当前Project目录 ; 也可使用绝对路径
val value = new PrintWriter(new File("output/test.txt"))
value.write("Hello Scala")
value.close()
网络
Scala进行网络数据交互时, 采用的也依然是Java中的I/O类。
// 服务器类
object TestServer {
def main(args: Array[String]): Unit = {
val server = new ServerSocket(9999)
while ( true ) {
val socket: Socket = server.accept()
val reader = new BufferedReader(
new InputStreamReader(
socket.getInputStream,
"UTF-8"
)
)
var s : String = ""
var flg = true
while ( flg ) {
s = reader.readLine()
if ( s != null ) {
println(s)
} else {
flg = false
}
}
}
}
}
// 客户端类
object TestClient {
def main(args: Array[String]): Unit = {
val client = new Socket("localhost", 9999)
val out = new PrintWriter(
new OutputStreamWriter(
client.getOutputStream,
"UTF-8"
)
)
out.print("hello Scala")
out.flush()
out.close()
client.close()
}
}
数据类型
Scala是完全面向对象的语言, 所以不存在基本数据类型的概念, 有的只是任意值对象类型(AnyVal)和任意引用对象类型(AnyRef)。
类型转换
自动类型转换
// Char → Int
// Byte → Short → Int → Long → Float → Double
val c1 : Char = 'A'
val c2 : Int = c1
val c3 : Byte = 10
val c4 : Short = c3
val c5 : Int = c4
强制类型转换
使用方法的方式进行类型间转换。
var a : Int = 10
Var b : Byte = a.toByte
字符串类型转换
// 所有类型都提供了toString()方法, 可以直接转换为字符串
val i : Int = 10
val s1 : String = i1.toString
// 所有类型都提供了和字符串进行拼接的方法 +
val s2 = i.+(s1)
val s3 = i + s1
运算符
Scala中其实是没有运算符的, 所有意义上的运算符都是方法。
- Scala是完全面向对象的语言, 所以数字其实也是对象。
- 当调用对象的方法时, “.” 可以省略
- 如果函数的参数只有一个, 或没有, “()” 可以省略
例如 :
val i = 1.+(2)
val i = 1 + 2
算术运算符
假设变量A为10, B为20。
那么 :
关系运算符
假设变量A为10, B为20。
那么 :
赋值运算符
逻辑运算符
假设变量A为1, B为0。
那么 :
位运算符
假设变量A为60, B为13, 二者对应的二进制位 A = 00111100 ; B = 00001101
那么 :
流程控制
分支控制
让程序有选择的执行。分支控制有三种: 单分支、双分支、多分支
单分支
if (布尔表达式) {
// 如果布尔表达式为true, 则执行该语句块
}
双分支
if (布尔表达式) {
// 如果布尔表达式为true, 则执行该语句块
} else {
// 如果布尔表达式为false, 则执行该语句块
}
多分支
if (布尔表达式1) {
// 如果布尔表达式1为true, 则执行该语句块
} else if (布尔表达式2) {
// 如果布尔表达式2位true, 则执行该语句块
} ...
} else {
// 如果上面条件都不满足, 则执行该语句块
}
实际上, Scala中的表达式都是有返回值的, 例如
object ScalaBranch {
def main(args: Array[String]): Unit = {
val age = 30
val result = if ( age < 18 ) {
"童年"
} else if ( age <= 30 ) {
"青年"
} else if ( age <= 50 ) {
"中年"
} else {
"老年"
}
println(result)
}
}
循环控制
for循环
- 语法
for (循环变量 <- 数据集) {
// 循环体
}
object ScalaLoop {
def main(args: Array[String]): Unit = {
for ( i <- Range(1,5) ) { // Range(1, 5) 范围集合 [1, 5)
println("i = " + i )
}
for ( i <- 1 to 5 ) { // 1 to 5 => [1, 5]
println("i = " + i )
}
for ( i <- 1 until 5 ) { // 1 until 5 => [1, 5)
println("i = " + i )
}
}
}
- 循环守卫
循环时可以增加条件来决定是否继续循环体的执行, 这里的判断条件称为循环守卫
object ScalaLoop {
def main(args: Array[String]): Unit = {
for ( i <- Range(1,5) if i != 3 ) { // [1, 5) & i != 3 => 1, 2, 4
println("i = " + i )
}
}
}
- 循环步长
Scala的集合也可以设定循环的增长幅度, 也就是所谓的步长step
object ScalaLoop {
def main(args: Array[String]): Unit = {
for ( i <- Range(1,5,2) ) { // [1, 5) & step = 2 => 1, 3
println("i = " + i )
}
for ( i <- 1 to 5 by 2 ) { // [1, 5] & step = 2 => 1, 3, 5
println("i = " + i )
}
for ( i <- 1 until 5 by 2 ) { // [1, 5) & step = 2 => 1, 3
println("i = " + i )
}
}
}
- 循环嵌套
两层循环可写进一层内
object ScalaLoop {
def main(args: Array[String]): Unit = {
for ( i <- Range(1,5); j <- Range(1,4) ) {
println("i = " + i + ",j = " + j )
}
for ( i <- Range(1,5) ) {
for ( j <- Range(1,4) ) {
println("i = " + i + ",j = " + j )
}
}
}
}
- 引入变量
在循环条件中可引入变量
object ScalaLoop {
def main(args: Array[String]): Unit = {
for ( i <- Range(1,5); j = i - 1 ) {
println("j = " + j )
}
}
}
> 题目 : 如何只使用一次for循环实现九层妖塔。
val sum = 9
for (i <- Range(0, sum); j = 2 * i + 1) {
println(" " * (sum - i - 1) + "*" * j)
}
while循环
- 语法
while( 循环条件表达式 ) {
循环体
}
object ScalaLoop {
def main(args: Array[String]): Unit = {
var i = 0
while ( i < 5 ) {
println(i)
i += 1
}
}
}
do…while循环
do {
循环体
} while ( 循环条件表达式 )
object ScalaLoop {
def main(args: Array[String]): Unit = {
var i = 5
do {
println(i)
} while ( i < 5 )
}
}
循环中断
Scala是完全面向对象的语言, 取消了break, continue关键字, 采用函数式编程的方式代替
object ScalaLoop {
def main(args: Array[String]): Unit = {
scala.util.control.Breaks.breakable {
for ( i <- 1 to 5 ) {
if ( i == 3 ) {
scala.util.control.Breaks.break
}
println(i)
}
}
}
}
嵌套循环
循环中有循环, 就是嵌套循环。
var i = 1
var j = 1
val sum = 9
while (i <= sum) {
j = i
while (j <= sum) {
print(s"$i*$j=${i * j}\t")
j += 1
}
println()
i += 1
}
函数式编程
Scala是完全面向对象编程, 同时也是完全函数式编程。
> 面向对象编程 : 分解对象, 行为, 属性, 然后通过对象的关系及行为的调用来解决问题。
> 函数式编程 : 将问题分解为一个一个的步骤, 将每个步骤进行封装(函数), 通过调用这些封装好的功能按照指定的步骤, 解决问题。
函数编程 - 基础篇
- 基本语法
[修饰符] def 函数名 (参数列表) [:返回值类型] = {函数体}
def test(str: String): Unit = {
println(str)
}
-
Scala中存在方法与函数两个不同的概念
方法是类的一部分, 反之, 在类中定义的函数即是方法, 仍然有重载和重写
函数是一个对象, 可以赋值给一个变量, 不是在类中定义的就是函数, 没有重载和重写 -
函数的定义
- 无参, 无返回值
def test1(): Unit = {
}
println(s"test1 : ${test1()}")
- 无参, 有返回值
def test2(): String = {
"hello"
}
println(s"test2 : ${test2()}")
- 有参, 无返回值
def test3(i: Int): Unit = {
}
println(s"test3 : ${test3(1)}")
- 有参, 有返回值
def test4(i: Int): Int = {
i
}
println(s"test4 : ${test4(1)}")
- 多参, 无返回值
def test5(i: Int, s: String): Unit = {
println(s"i : $i ; s : $s")
}
test5(1, "hello")
- 多参, 有返回值
def test6(i: Int, s: String): String = {
s"i : $i ; s : $s"
}
println(test6(1, "hello"))
-
函数参数的个数最多是 22 个
声明时可以超过22个
但是将函数作为对象赋值给变量时会报错 -
可变参数 String*
可变参数必须放置在参数列表的最后
def test7(s: String*): Unit = {
for (elem <- s) {
println(elem)
}
}
test7("1", "2", "3")
- Scala中函数的参数默认使用val声明, 无法修改
def test00(s: Int): Unit = {
//s = 1 // error : Reassignment to val
}
- 参数默认值
调用时, 可以给设置默认值的参数赋值, 则该参数值被覆盖
调用时, 可以不给设置默认值的参数赋值, 则该参数值为默认值
def test8(s: String, i: Int = 1): Unit = {
println(s"s : $s ; i : $i")
}
test8("1")
test8("2", 3)
- 带名参数, 指明要赋值的参数
def test9(s: String = "000", i: Int): Unit = {
println(s"s : $s ; i : $i")
}
test9(i = 1)
函数式编程 - 噩梦篇
- Scala 中函数有一个原则 : 至简原则
- 省略 return
当函数需要返回值时, 可以将函数体中最后一行执行的代码作为返回结果, 所以可以省略return关键字
def test1(): String = {
"test1"
}
println(test1())
- 省略返回值类型
如果编译器可以推断出函数的返回值类型, 那么返回值类型可以省略
def test2() = {
"test2"
}
println(test2())
- 省略 {}
如果函数体中只有一行代码, 那么大括号可以省略
def test3() = "test3"
println(test3())
- 省略 ()
如果函数没有提供参数, 那么调用时, 小括号可以省略
如果函数没有提供参数, 那么声明时, 小括号可以省略, 且调用时, 也必须省略
def test4 = "test4"
println(test4)
- 省略 = : 过程函数(procedure)
函数如果明确使用Unit声明, 那么函数体中的return关键字不起作用
函数体中如果明确使用return关键字, 那么声明时返回值类型不能省略
def test5: Unit = return "test5"
println(test5)
// 明确函数就是没有返回值, 但是Unit又不想声明, 那么此时就必须连等号一起省略, 但是大括号不能省略
def test6 {
return "test6"
}
println(test6)
- 省略 函数名 和 def : 匿名函数
当只关心代码逻辑, 而不关心函数名时, 函数名和def关键字可以省略
需要调用的话, 可以将函数赋值给一个变量
val t = () => "test7"
println(t())
函数式编程 - 地狱篇
函数是函数式编程中最重要的概念, “一等公民”。
Scala是完全面向对象编程, 所以函数也是对象。
- 函数可以作为对象赋值给变量
def test1(): String = {
"zhang san"
}
// 将函数赋值给变量, 那么这个变量其实就是函数, 可以调用
// 函数如果没有参数列表, 那么调用时可以省略 "()"
// 如果此时希望将函数不执行, 而是当成一个整体赋值给变量, 那么需要使用 "_"
val f1 = test1() // f1 : String // 变量类型
val f2 = test1 _ // f2 : () => String // 方法类型
println(f2()) // zhang san
// 另外的解决方法 : 指定该变量的类型为函数类型 参数列表 => 返回值类型
val f3: () => String = test1
println(f3()) // zhang san
- 函数可以作为参数传递给其他的函数
def test2(name: () => String): String = {
name()
}
// 将函数赋值为变量
val result1 = test1 _
println(test2(result1))
// 将函数作为参数使用时, 一般不关心函数的名称, 所以一般使用匿名函数
// 匿名函数规则 : (参数列表) => {代码逻辑}
val result2 = test2(() => "li si")
println(result2)
println(test2(() => "li si"))
至简原则
def test4(age: Int, out: Int => String): String = {
out(age + 1)
}
val result3 = test4(20, (add: Int) => {
s"Next year's age : $add"
})
// 如果逻辑代码只有一行, "{}" 可以省略
val result4 = test4(20, (add: Int) => s"Next year's age : $add")
// 如果匿名函数的参数类型可以推断出来, 那么类型可以省略
val result5 = test4(20, (add) => s"Next year's age : $add")
// 如果匿名函数的参数列表只有一个或没有, 那么 "()" 可以省略
val result6 = test4(20, add => s"Next year's age : $add")
// 如果匿名函数中参数在逻辑代码中只使用了一次, 那么参数和=>可以省略
// 使用下划线代替参数
val result7 = test4(20, "Next year's age : " + _)
// val result8 = test4(20,s"Next year's age : $_")
def sum(x: Int, y: Int): Int = {
x + y
}
def calcAnalysis(f: (Int, Int) => Int): Int = {
val boyCnt = 23
val girlCnt = 25
f(boyCnt, girlCnt)
}
val f = sum _
println(calcAnalysis(f))
calcAnalysis((x: Int, y: Int) => {
x + y
})
// 简化 ↓
calcAnalysis((x: Int, y: Int) => x + y)
calcAnalysis((x, y) => x + y)
calcAnalysis(_ + _) // 最简版
函数可以作为函数的返回值返回
def test1(i: Int): Int = {
i * 2
}
def fun1(): Int => Int = {
test1 _
}
// 调用
val a = fun1() // 当前fun1函数的执行结果为函数, 那么此时a就是函数
println(a(10))
// 调用
println(fun1()(10))
- 当函数作为返回值使用时, 一般会使用嵌套函数
def fun2() = {
def test2(i: Int): Int = {
i * 2
}
test2 _
}
// 调用
println(fun2()(10))
// 如果不想使用下划线返回对象, 那么需要显示声明函数的返回值类型
def fun3(): Int => Int = {
def test2(i: Int): Int = {
i * 2
}
test2
}
闭包 : 改变生命周期
-
Scala中, 执行方法时, 会有压栈, 弹栈, 栈帧的概念, 先进后出
当Scala中的函数在执行时, 会先被编译成Java中的方法, 才能在JVM上运行
所以, 下面的代码, 会先执行test3(压栈), 弹栈后(局部变量失效), 执行sum(压栈), 此时的i是怎么从test3传到sum中的呢?此时需要延长变量i的生命周期。
函数在使用外部变量时, 如果外部变量失效时, 会将这个变量包含到当前函数的内部, 形成闭合的使用效果, 改变了变量的生命周期。
将这种操作称之为closure(闭包) -
Scala2.12 : 底层编译器重新声明了内部函数的参数, 将使用的外部变量作为内部函数的参数使用。“不同的i, 相同的值”
Scala2.11 : 底层闭包会被编译为匿名函数类, 如果使用外部变量, 会将外部变量作为类的属性供内部函数使用。即使没有使用外部的变量, 可能也会有闭包的效果, 只是没有包含外部的变量。 -
匿名函数肯定为闭包, 将函数赋值给变量使用也是闭包, 嵌套的内部函数在外部使用也是闭包。
-
Spark如何判断闭包 : 判断类名中是否为匿名函数类
def test3(i: Int) = {
def sum(j: Int) = {
i + j
}
sum _
}
println(test3(10)(20))
函数的柯里化 : 多个参数列表
所谓的柯里haul, 其实就是多个参数列表
- 简化嵌套函数开发
- 将复杂的参数逻辑简单化, 可以支持更多的语法
// 使用函数柯里化的方式声明函数
def test4(i: Int)(j: Int)(f: (Int, Int) => Int): Int = {
f(i, j)
}
println(test4(10)(20)(_ + _))
控制抽象 : 将代码逻辑作为参数传递给函数
- 控制抽象时写框架必不可少的语法
// 参数列表中如果有多行逻辑, 那么可以采用大括号代替小括号
// 举例 : breakable是一个函数
breakable { // 捕捉异常
for (i <- 1 to 5) {
if (i == 3) {
break //抛出异常
}
println("i = " + i)
}
}
println("main...")
- 如果函数参数想传递代码逻辑, 那么类型声明方式为 :
参数名 : => 返回值类型
def test5(f: => Unit): Unit = {
f
}
// 调用
// 因为参数类型中没有声明小括号, 所以调用时, 也不能加小括号
test5(println("test5..."))
递归 - 普通递归
- 方法执行过程中调用自身
- 存在可以跳出递归的逻辑, 不然可能包StackOverflowError(栈溢出)
- 方法调用时, 传递的参数之间应该存在规律
- Scala中递归方法需要明确返回值类型
// 阶乘 : 一个大于1的数的阶乘等于这个数乘以自身减一的阶乘
val num = 10
def factorial1(num: Int): Int = {
if (num < 1) {
1
} else {
num + factorial1(num - 1) // 此处的结果需要依据下一层函数的结果, 会导致方法连续压栈
}
}
println(factorial1(5)) // 15
println(factorial1(100000)) // StackOverflowError
// 原因 : 递归太深 → 方法太多 → 栈帧太多 → 栈溢出
递归 - 尾递归
- 在逻辑的最后一行调用自身, 且递归的方法不依赖外部的变量
- 编译器会将该代码逻辑自动优化为while循环
def factorial2(num: Int, result: Int): Int = {
if (num < 1) {
result
} else {
factorial2(num - 1, num + result)
}
}
println(factorial2(5, 1)) // 15
println(factorial2(100000, 1)) //705082705
惰性函数 : 延迟加载
def test6():String={ // ① 如果此处返回的是10000条User数据
println("xxx")
"zhang san"
}
val name1 = test6() // ② 此处加载该方法, 却没使用, 导致大量数据占用内存
println("*********") // ③ 如果此过程执行了1个小时, 那么会导致内存长时间占用
println("name = " + name1) // ④ 使用并释放内存
//xxxxxx
//*********
//name = zhang san
- 解决 : 使用惰性函数延迟加载, 则当用到数据时才加载数据
- 底层lazy延迟加载功能是编译器在编译时产生大量的方法进行调用实现的。
lazy val name2 = test6()
println("*********")
println("name = " + name2)
//*********
//xxxxxx
//name = zhang san
面向对象编程
基础面向对象编程
面向对象编程 - 包 (package)
- Java中
- 声明包
域名 + 项目名称 + 模块名 + 程序类型名称 - package的作用 :
2.1 管理类
2.2 区分类
2.3 访问权限
2.4 编译类
Java语法中, package包语法的作用没有那么强大
- Scala语言对package语法进行了功能的补充, 使之更加强大
- package和源码文件的物理路径没有关系
Scala会按照package的声明编译指定路径的class, 源码路径随便放 - package关键字可以多次声明
当前类编译后会在最后的package所在的包中生成class文件
//package cn.lofe.test.Package
package cn
package lofe
package scala
package test
- package可以使用层级结构
在package包名的后面增加大括号, 设定作用域范围以及层级关系
子包不需要import父包就可以使用父包的内容(作用域)
package test{
package childTest{
object Package{
}
}
}
- 包也可以当成对象来使用
package中为了和Java兼容, 默认不能声明属性和函数(方法), 但是是可以声明类的
Scala提供了一种特殊的对象声明方式 : 包对象
该对象的内容只供当前包内的所有类使用
可以将一个包中共同性的方法或属性在包对象中声明
面向对象编程 - import
- Java中
- Java中的import作用是导类
import java.sql.* - Java中引入了import static静态导入, 导入类的静态属性和静态方法
- Scala语言在Java语法基础上进行了扩展
- import可以声明在任意地方
- Java中默认导入的类 : java.lang包下的类
Scala中默认导入的类 : java.lang包下的类 ; scala包中的类 ; Predef(类似于Java中的静态导入) - Scala中的import可以导包
import java.sql
val date = new sql.Date()
- 导入一个包中所有的类
Scala中使用下划线代替java中的星号
import java.sql._
- 可以在一行中导入同一个包的多个类
import java.util.{ArrayList, HashMap}
- 使用import关键字将包中的类隐藏掉
import java.sql.{Date=>_, _} // 导入所有的类及隐藏Date类
- 使用import关键字给指定类起别名
import java.util.{Date=>UtilDate}
// 或
type UtilDate = java.util.Date
// 如 : Scala中没有字符串, 直接使用java中的字符串
val s:String = "abc" // 底层 : type String = java.lang.String
- Scala默认import是按照当前包的相对路径进行导入的。
此时双亲委派机制没起作用
如果不想使用相对路径, 可以采用特殊路径(root根路径)访问
new java.util.HashMap // 当前包下的HashMap类
new _root_.java.util.HashMap // java自带的HashMap类
- Scala中可以采用import关键字导入对象
导入对象只能对val声明的对象进行导入, var是不可以的
val user = new User()
import user._
test1()
test2()
- 双亲委派机制
类加载过程 :加载前会判断执行的位置, 然后调用相应的加载器进行加载
应用类加载器加载前会委托扩展类加载器加载, 扩展类加载器委托启动类加载器加载
而此时, 启动类加载器中有JDK自带的类, 则加载该类
如果启动类加载器中没有该类, 则会委托扩展类加载器加载, 扩展类加载器委托应用类加载器
如果还找不到该类, 则报异常
面向对象编程 - class
- class和object的区别
- object在编译时会产生两个类文件
当前类的文件 和 单例的类文件
> 如 : Test_Class.class , Test_Class$.class
class在编译时只会产生当前类的class文件
> 如 : Test_Class.class
2.class用来修饰普通的类, 而object用于修饰伴随着这个类所产生的一个单例对象, 用于模仿java中的静态语法。
object中的方法和属性可以通过类名直接访问, 类似于静态语法 - 一般将使用object声明的类称之为伴生类, 对应的那个单例对象称之为伴生对象
后统一将使用class声明的类称之为伴生类, 使用object声明的类为伴生对象 - IDEA中如果一个Scala源码文件的图标为黄色的o, 表示这个文件中只有伴生对象。
IDEA中如果一个Scala源码文件的图标为蓝黄相间的c, 表示这个文件中有伴生对象和伴生类。
IDEA中如果一个Scala源码文件的图标为灰色的文件图标和三条橘色的线, 表示这个文件内容很杂。
IDEA中如果一个Scala源码文件的图标为蓝色的c和三条橘色的线, 表示这个文件中只有类(伴生类)。
- 问题 : Thread的sleep和wait的区别?
核心区别 : sleep是静态方法 、wait是成员方法
静态方法和类型相关, 和对象无关
成员方法和对象相关
Thread t1 = new Thread;
Thread t2 = new Thread;
t1.start;
t2.start;
// sleep方法和t1无关, 就不可能让t1休眠, 而是让当前执行该方法的线程(main)休眠。
// 所以 sleep无法释放对象锁
t1.sleep(1000);
// wait方法和t2相关, 所以会让t2等待
// 所以wait可以释放对象锁
t2.wait();
- Scala中class可以继承(extends)父类
Scala当省略类型时, 编译器会自动将构建对象的类型进行推断
val child = new Child()
如果需要使用多态操作, 那么必须显示声明类型
val child : Parent = new Child()
面向对象编程 - field
object Field {
def main(args: Array[String]): Unit = {
// 在类中声明属性, 就等同于在类中声明局部变量的感觉, 可以使用var/val声明, 可以通过对象在外部访问
val user = new User
user.name = "zhang san"
user.age = 23
println(user.name + user.age)
// Scala中声明的属性, 在编译时, 都是private修饰的, 并同时提供了公共的get/set方法
// 但是, 其命名方式并没有遵循bean规范, 这样会在很多框架中无法使用
// 可以采用在声明属性时添加注解的方式使其在编译时额外生成遵循bean规范的get/set方法, @BeanProperty
// 使用属性时, 获取值和赋值时, 相当于调用了get/set方法
// 使用val声明的属性, 在编译时还会使用final修饰, 且没有set方法
}
class User {
// 属性
// 变量应该显示的初始化
var name: String = _
// 如果想要像java中类的属性初始化一样, 需要采用特殊的符号 : 下划线
@BeanProperty
var age: Int = _
// 如果属性使用val来声明, 那么初始值不能使用下划线
}
}
面向对象编程 - 访问权限
class User01 {
/*
Java : 4中访问权限
private : 私有权限 同类
(default) : 包权限 同类, 同包
protected : 受保护权限 同类, 同包, 子类
public : 公共权限 任意地方
Scala : 4种访问权限
(default) : 公共权限 任意地方
private : 私有权限 同类
private[包名] : 包私有权限 同类, 同包
protected : 受保护权限 同类, 子类
*/
// 如果属性声明为private, 在编译时生成的set/get方法也会使用private修饰
private val name: String = "zhang san"
// 使用@BeanProperty注解后, 属性不能声明为private
@BeanProperty
val age: Int = 23
// 包名可以向上使用, 但必须在当前包的作用域范围内使用
private[lofe] val sex: String = "man"
// Scala中的源码文件中可以声明多个公共类
}
class User02 {}
class USer03 {}
面向对象编程 - 方法
- 方法的重载 : \多个方法名相同, 但是参数列表(参数个数/参数类型/参数顺序)不相同
数值类型, 在重载的方法中会提升精度
引用类型, 在重载的方法中如果找不到对应的类型, 会从类树往上查找 - 方法的重写 : 方法的重写一定要存在父子类, 子类重写父类相同方法的逻辑
方法名一致, 参数列表保持一致, 异常范围不大于父类, 访问权限不低父类
既然父类和子类有相同的方法, 形成了方法的重写, 那么在调用时, 无法确定到底执行哪一个方法, 那么需要<动态绑定机制>
动态绑定机制 : 程序执行过程中, 如果调用了对象的成员方法时, 会将方法和对象的实际内存进行绑定, 然后调用。
动态绑定与属性无关。
object Method {
def main(args: Array[String]): Unit = {
val user = new User
// 面向对象 - 方法
// 所谓的方法其实就是在类中声明的函数
// 1. 默认方法
// java.lang.Object
user.toString
user.hashCode()
// scala中提供的方法
user.isInstanceOf[User]
user.asInstanceOf[User]
// Predef提供的方法
// 获取对象的类型信息(方法内存)
// val clazz: Class[_ <: User] = user.getClass
val clazz: Class[_ <: User] = classOf[User]
}
}
class User {}
- 面向对象编程 - 方法 - apply(应用)
object Method_Apply {
def main(args: Array[String]): Unit = {
// 面向对象 - 方法 - apply(应用)
// apply主要作用用于构建对象
// 私有的构造方法是没法通过new的方式构建对象的, 此时可以使用apply构建
val user2 = User.apply() // apply的方式构建对象
// 使用new, 等同于调用对象的构造方法构建对象
// 如果不使用new来构建对象, 那么等同于调用伴生对象的apply方法
val user1 = new User() // new的方式构建对象
// apply方法一般用于object伴生对象中构建对象
// 伴生对象可以访问伴生类的所有属性和方法
// Scala会自动识别apply方法, 所以调用伴生对象的apply方法时, apply方法名可以省略
val user3 = User()
// 如果将伴生对象不使用小括号操作, 那么等同于将伴生对象赋值给变量, 而不是调用apply方法
// apply方法如果想要被编译器自动识别, 那么不能省略小括号
val user4 = User
// apply方法主要用于构建对象, 但是这个构建对象不一定是当前类的对象
// apply方法可以重载
}
}
class User {
}
object User {
def apply(): User = {
new User()
}
}
-
< 方法的重载>
- 多个方法名相同, 但是参数列表(参数个数/参数类型/参数顺序)不相同
- 数值类型, 在重载的方法中会提升精度
- 引用类型, 在重载的方法中如果找不到对应的类型, 会从类树往上查找
-
<方法的重写>
- 方法的重写一定要存在父子类, 子类重写父类相同方法的逻辑, 要求方法名一致, 参数列表保持一致, 异常范围不大于父类, 访问权限不低父类
- 既然父类和子类有相同的方法, 形成了方法的重写, 那么在调用时, 无法确定到底执行哪一个方法, 则需要<动态绑定机制>
- 动态绑定机制 : 程序执行过程中, 如果调用了对象的成员方法时, 会将方法和对象的实际内存进行绑定, 然后调用。动态绑定与属性无关。
> Scala中 : - 如果子类重写父类的完整方法, 需要显示增加override关键字修饰
override def test() : Unit = {}
- 如果子类重写父类的抽象方法, 直接补充完整即可
- Scala中属性也有重写的概念, 但只能重写val修饰的属性
- 子类重写父类的抽象属性, 那么需要将属性补充完整即可
- 子类重写父类的完整属性, 那么需要使用abstract修饰
- 子类重写父类的属性时, 该属性不能是可变的, 因为Scala中各类的属性在编译时, 会自动生成私有属性和对应的get/set方法
-
面向对象编程 - 构造方法
> 使用new关键字创建的对象其实等同于调用类的构造方法
> Scala是一个完全面向对象的语言, 同时还是一个完全面向函数的语言
> Scala中类其实也是一个函数, 类名其实就是函数, 类名后面可以增加括号, 表示函数参数列表
这个类名所代表的的函数其实就是构造方法/构造函数
构造方法执行时, 会完成类的主题内容的初始化
val user = new User()
class User {
}
> Scala中提供了两种不同类型的构造方法
- 主构造方法 : 在类名后的构造方法。可以完成类的初始化
- 辅助构造方法 : 为了完成类初始化的辅助功能而提供的构造方法
声明方法 : def this()
要求 : 在使用辅助构造方法时, 必须直接或间接的调用主构造方法
辅助构造也存在重载的概念
class User(){
def this(name:String)={
this()
}
def this(name:String, age:Int)={
this(name)
}
}
> 给构造方法增加参数的目的是从外部将数据传递到对象的属性中
> Scala中一般构造方法的参数用于属性的初始化, 所以为了减少数据的冗余, 可以使用关键字var/val将构造参数当成类的属性来使用
class User(var name:String){
}
> 构建子类对象时, 应该首先构建父类对象, 如果父类的构造方法有参, 那么子类在构建父类对象也应该有参。
> 需要在父类名称的后面加括号就可以传参了
class Person (name : String) {
}
class User(name : String) extends Person (name) {
}
例题 : 关于Object类中clone方法的调用
public class CloneTest {
public static void main(String[] args) {
// Object中的clone方法如下
// protected native Object clone() throws CloneNotSupportedException;
// protected : 受保护权限 ; 同类, 同包, 子类可调用
// 默认所有的类都会继承Object类, 因此会获取其中的所有属性和方法
// 那么, 在CloneTest类中通过创建对象的方式, 能否调用User类中的clone方法
User user = new User();
// user.clone; // 不能!
// 问题1. 方法的调用者 : cn.lofe.scalao1.day06.CloneTest
// 方法的提供者 : java.lang.Object
// 问题2. user.clone;中"."的作用 : 表示从属关系 : user对象的clone方法, 并不是调用的意思
// 显然, CloneTest类与Object并不同类, 也不同包, 但是却无法调用, 因此 :
// 问题3. CloneTest和Object没有父子关系
// 结论 : CloneTest和User类继承于Object类, 会分别加载Object类中的属性及方法,
// 当各自调用各自父类(Object)中的元素时, 相安无事, 当CloneTest调用user对象的clone方法时,
// 由于CloneTest类与User类的父类(Object)并无继承关系, 故无法调用
}
}
class User {
}
高阶面向对象编程
面向对象编程 - 抽象
抽象的对象一般理解为不完整的对象
- 抽象类 : 使用abstract修饰
- 抽象方法 : 只有声明而没有实现的方法, 无需使用abstract修饰
- 抽象属性 : 属性只有声明, 没有初始化
> Scala中引入了**<抽象属性>和<属性的重写>**的概念
> 如果一个类中有抽象方法/属性, 那么这个类一定是抽象类
> 如果一个类是抽象类, 但内部不一定有抽象方法/属性
> 抽象类无法直接构造对象, 只能被继承
> 如果子类继承抽象类, 必须实现父类的抽象方法/属性, 否则仍为抽象类
> 若子类重写父类的抽象属性, 只需将属性补充完整即可
> 若子类重写父类的完整属性, 则需使用abstract修饰
> 抽象属性在编译时, 不会产生类的属性, 而是产生属性的set/get方法, 且是抽象的
> 重写属性, 等同于普通属性的声明 : 属性、set、get
abstract class User { // 抽象类
var name : String // 抽象属性
var age : Int = 23 // 完整属性
def test() : Unit // 抽象方法
}
abstract class SubUser1 extends User {
// 未实现父类的抽象方法, 故仍为抽象类
}
class SubUser2 extends User {
var name : String = "zhang san" // 重写父类的抽象属性
// 重写父类的完整属性时, 该属性不能是可变的
// abstract var age : Int = 24 // error
abstract val age : Int = 24 // 重写父类的完整属性
def test() : Unit = {} // 实现了父类的抽象方法, 则该类非抽象类, 可被继承
}
面向对象编程 - 特质(接口)
Java中 :
- 抽象类 : 将子类共通的内容给抽取出来, 放在父类中
- 父类不能直接构建对象, 必须通过子类来创建, 所以声明为抽象类
- 抽象类一般就在父子继承和方法重写的时候用的比较多
- 接口 : 和类不一样, 不是一个体系, 可以多继承
- 将接口理解为规范和规则。 如果一个类符合指定的规则, 就应该实现该接口
public class InterfaceTest {
public static void main(String[] args) {
Test test = new User(); // User继承Person, Person实现Test, 那么User实现Test?
System.out.println(User.class.getInterfaces().length); // User实现接口的数量: 0
// 结论 : User并没有实现Test接口, 而是多态的传递
}
}
interface Test {
}
class Person implements Test{
}
class User extends Person {
}
Scala中 :
- 没有接口的概念, 也就没有interface关键字
- Scala可以将多个类中相同的特征从类里剥离出来, 形成特殊的语法结构, 称之为"特质", 使用关键字trait声明。
- 如果一个类符合这个特质, 那么就可以将这个特质混入到类中, 使用extends关键字
// 声明特质
trait Operate {
// 特质中可以声明抽象方法
def oper () : Unit
}
// 类混入特质
class MySQL extends Operate{
// 实现特质中的抽象方法
override def oper(): Unit = {}
}
- 可将特质理解为接口
- 特质之间可以继承, 且是多继承的, 使用with关键字连接
- 可将特质理解为抽象类,
- 特质可以继承其他类, 并可以混入其他特质, 使用with关键字连接
- 如果特质混入类中, 需使用extends或with关键字
- 将特质理解为抽象类, 那么就可以被继承(extends), 且可以混入其他特质或类(with)。
- 如果特质为抽象的, 那么混入的类应该实现其父类的抽象方法/属性, 否则仍为抽象类
- 如果特质中存在具体方法, 重写父类的方法/属性时, 需要使用override关键字
- Scala中可以直接使用java中的代码, 由于scala中没有接口, 故java中所有的接口在scala中都当成特质来用
trait ParentTest1 {}
trait ParentTest2 {}
trait Test extends Exception with ParentTest1 with ParentTest2 {
// 抽象方法
def test(): Unit
// 具体方法
def test1(): Unit = {
println("test...")
}
}
class User extends Test with ParentTest1 with java.io.Serializable {
def test(): Unit = {}
override def test1(): Unit = {
print("test...")
}
}
- 动态混入
- 动态混入(动态扩展)完全遵循OCP(开闭原则)开发原则
- 特质中不仅仅有抽象方法, 还可以有具体的方法
- 如果对象声明后想要扩展功能, 可以不需修改源码, 直接使用动态混入实现
object Interface {
def main(args: Array[String]): Unit = {
val mysql = new MySQL with Operate
mysql.insert()
}
}
trait Operate {
def insert() : Unit = {
println("insert data...")
}
}
class MySQL {
def select() : Unit = {
print("select data...")
}
}
- 初始化顺序
类/特质的初始化顺序 :
- 类如果有父类, 而父类混入特质1, 而特质1又继承特质2
- 那么特质2会先初始化
- 然后特质1再初始化
- 接下来父类初始化
- 如果当前类还有特质的话, 那么特质会初始化
- 当前类初始化
object Interface {
def main(args: Array[String]): Unit = {
new SubUser() // abdce
}
}
trait Parent {
print("a")
}
trait Test extends Parent {
print("b")
}
trait SubTest extends Parent {
print("c")
}
class User extends Test {
print("d")
}
class SubUser extends User with SubTest {
print("e')
}
如果类混入多个特质时, 特质的初始化顺序为从左到右
object Interface {
def main(args: Array[String]): Unit = {
new User() // abcd
}
}
trait Test1 {
println("a")
}
trait Test2 {
println("b")
}
trait Test3 {
println("c")
}
class User extends Test1 with Test2 with Test3 {
println("d")
}
- 功能执行顺序
类/特质的功能执行顺序 :
- 如果类混入多个特质时, 功能的执行顺序为从右向左的
- 特质中的super其实有特殊的含义, 表示的不是父类特质, 而是上级特质
- 如果想要改变执行顺序, 需要制定特质的类型
object Interface {
def main(args: Array[String]): Unit = {
new MySQL().operData() // 向数据库中操作数据...
}
}
trait Operate {
def operData(): Unit = {
println("操作数据...")
}
}
trait DataBase extends Operate {
override def operData(): Unit = {
print("向数据库中")
super.operData()
}
}
class MySQL extends DataBase {}
输出结果 : 向数据库中操作数据…
object Interface {
def main(args: Array[String]): Unit = {
new MySQL().operData() // 向日志中向数据库中操作数据
}
}
trait Operate {
def operData(): Unit = {
println("操作数据...")
}
}
trait DataBase extends Operate {
override def operData(): Unit = {
print("向数据库中")
super.operData()
}
}
trait Log extends Operate {
override def operData(): Unit = {
print("向日志中")
super.operData() // 调用上级特质
}
}
class MySQL extends DataBase with Log {
}
输出结果 : 向日志中向数据库中操作数据…
如果想要改变执行顺序, 需要制定特质的类型
object Interface {
def main(args: Array[String]): Unit = {
new MySQL().operData() // 向日志中操作数据
}
}
trait Operate {
def operData(): Unit = {
println("操作数据...")
}
}
trait DataBase extends Operate {
override def operData(): Unit = {
print("向数据库中")
super.operData()
}
}
trait Log extends Operate {
override def operData(): Unit = {
print("向日志中")
// 如果想要改变执行顺序, 需要制定特质的类型
super[Operate].operData() // 调用Operate的OperData()
}
}
class MySQL extends DataBase with Log {
}
输出结果 : 向日志中操作数据…
扩展
- 枚举
object Color extends Enumeration {
val RED = Value(1, "red")
val YELLOW = Vlaue(2, "yellow")
val BLUE = Vlaue(2, "blue")
}
mian {
println(Color.RED)
println(Color.BLUE)
}
- 应用类
object Test extends APP {
println("xxx")
println("yyy")
}
- 类型检查和转换
- 判断对象是否为某个类型的实例
val bool: Boolean = person.isInstanceOf[Person]
- 将对象转换为某个类型的实例
val p1: Person = person.asInstanceOf[Person]
- 获取类的信息
val pClass: Class[Person] = classOf[Person]
集合
Scala的集合有三大类 : 序列Seq、集Set、映射Map, 所有集合都扩展自Iterable特质。
对于几乎所有的集合类, Scala都同时提供了可变和不可变的版本。
可变和不可变集合一般可根据集合所在包名进行区分
scala.collection.immutable(不可变)
scala.collection.mutable(可变)
默认情况下, Scala提供的集合都是不可变的。immutable
- 可变数组
- 内存存储的数据可以动态操作, 而不会产生新的集合
- 可变数组提供了大量对数据操作的方法, 基本是方法名都是英文单词
- 不可变数组
- 对数据的操作都会产生新的集合
- 提供对数据的操作方法相对来说较少, 而且都是一些符号
数组
不可变数组
// 创建数组
// val array = new Array[Int](4)
val array = Array[Int](4)
println(array.length) // 4
// 使用()访问数组元素
array(0) = 1
array(1) = 2
array(2) = 3
array(3) = 4
// 遍历数组中元素
for (elem <- array) {
println(elem)
}
array.foreach(elem => print(elem))
// 至简原则
array.foreach(println)
// 按照规则生成字符串
println(array.mkString(" ")) // 1 2 3 4
// Array属于不可变数组
// 对不可变集合的数据操作会产生新的数组
// 使用apply的方式, 创建数组
val array1 = Array(1, 2, 3, 4) // 1, 2, 3, 4
val array2 = Array(1, 2, 3, 4)
// 添加操作
// :+ 向数组的后面添加数据
val newArray1: Array[Int] = array1 :+ 5 // 1, 2, 3, 4, 5
// +: 向数组的前面添加数据
// 如果集合的方法采用冒号结尾, 那么运算结果从右向左执行
val newArray2: Array[Int] = newArray1.+:(6) // 6, 1, 2, 3, 4, 5
val newArray3: Array[Int] = 7 +: newArray2 // 7, 6, 1, 2, 3, 4, 5
// ++ 将集合的数据添加到当前集合
val newArray4: Array[Int] = array1 ++ array2 // 1, 2, 3, 4, 1, 2, 3, 4
// 向数组[Int]中添加字符串
val newArray5: Array[Any] = array :+ "5" // 1, 2, 3, 4, 5
// 判断集合是否为空
println(array.isEmpty) // false
// 工具类
// 合并数组
val newArray6: Array[Int] = Array.concat[Int](array1, array2) // 1, 2, 3, 4, 1, 2, 3, 4
// 创建指定范围的数组
val array3: Array[Int] = Array.range(0, 4) // 0, 1, 2, 3
// 创建并填充指定数量重复元素的数组
val array4: Array[Int] = Array.fill[Int](5)(0) // 0, 0, 0, 0, 0
// 多维数组
val array5: Array[Array[Int]] = Array.ofDim[Int](3, 3)
array5(0) = Array[Int](1)
array5(1) = Array[Int](1, 2)
array5(2) = Array[Int](1, 2, 3)
// 添加新元素
val array6: Array[Array[Int]] = array5:+Array[Int](1, 2, 3, 4)
// 遍历数组
array6.foreach(array => println(array.mkString(", ")))
可变数组
// 创建数组
// 可变数组不能通过new的方式创建数组
var arrayBuffer: ArrayBuffer[Int] = ArrayBuffer[Int](1, 2, 3, 4)
// 访问数据
println(arrayBuffer(1))
// 追加数据
arrayBuffer.append(5) // 1, 2, 3, 4, 5
// 向指定的位置(索引)插入数据
arrayBuffer.insert(1, 6, 7) // 1, 6, 7, 2, 3, 4, 5
// 修改数据
arrayBuffer(1) = 8 // 1, 8, 7, 2, 3, 4, 5
arrayBuffer.update(2, 9) // 1, 8, 9, 2, 3, 4, 5
// 删除数据
arrayBuffer.remove(1, 2) // 1, 2, 3, 4, 5
arrayBuffer.remove(4) // 1, 2, 3, 4
// 添加数据
arrayBuffer += 5 // 1, 2, 3, 4, 5
arrayBuffer +:= 0 // 0, 1, 2, 3, 4, 5
// 添加数组元素
val newArrayBuffer: ArrayBuffer[Int] = arrayBuffer ++ ArrayBuffer[Int](6, 7) // 0, 1, 2, 3, 4, 5, 6, 7
arrayBuffer ++= ArrayBuffer[Int](6, 7) // 0, 1, 2, 3, 4, 5, 6, 7
// 遍历数组
arrayBuffer.foreach(println)
// 判断集合是否为空
println(arrayBuffer.isEmpty) // false
// 可变数组 => 不可变数组
val array1: Array[Int] = arrayBuffer.toArray
// 不可变数组 => 可变数组
val array2: mutable.Buffer[Int] = array1.toBuffer
Seq集合
不可变List
// 创建集合
// 默认不可变集合List是抽象类, 无法使用new的方式进行实例化, 可以采用apply的方式
val list = List(1, 2, 3, 4)
// 访问数据
println(list(1)) // 2
// 增加数据
val newList1: List[Int] = list :+ 5 // List(1, 2, 3, 4, 5)
val newList2: List[Int] = list.+:(5) // List(5, 1, 2, 3, 4)
val newList3: List[Int] = 5 +: list // List(5, 1, 2, 3, 4)
// 连接集合(合并集合)
val newList4: List[Int] = list ++ List(5, 6) // List(1, 2, 3, 4, 5, 6)
val newList5: List[Int] = List.concat(list, List(5, 6)) // List(1, 2, 3, 4, 5, 6)
// 更新数据
val newList6: List[Int] = list.updated(1, 5) // List(1, 5, 3, 4)
// 遍历集合
list.foreach(println)
// 判断集合是否为空
println(list.isEmpty) // false
// 创建并填充指定数量重复元素的集合
val list4: List[Int] = List.fill[Int](4)(0) // List(0, 0, 0, 0)
// 空集合
val nil1: List[Nothing] = Nil // List()
val nil2: Nil.type = Nil // List()
// 空集合一般用于增加数据
val list1: List[Int] = 1 :: 2 :: 3 :: Nil // List(1, 2, 3)
val list2: List[Any] = 4 :: 5 :: list1 :: Nil // List(4, 5, List(1, 2, 3))
// 将list1拆分后添加到新集合, 这种操作称之为<扁平化>
val list3: List[Int] = 4 :: 5 :: list1 ::: Nil // List(4, 5, 1, 2, 3)
可变List
// 创建可变List
val listBuffer: ListBuffer[Int] = ListBuffer(1, 2, 3, 4)
// 访问数据
println(listBuffer(1))
// 增加数据
listBuffer.append(5) // ListBuffer(1, 2, 3, 4, 5)
listBuffer += 6 // ListBuffer(1, 2, 3, 4, 5, 6)
val newListBuffer1: ListBuffer[Int] = listBuffer :+ 7 // ListBuffer(1, 2, 3, 4, 5, 6, 7)
val newListBuffer2: ListBuffer[Int] = listBuffer.+:(7) // ListBuffer(7, 1, 2, 3, 4, 5, 6)
val newListBuffer3: ListBuffer[Int] = 7 +: listBuffer // ListBuffer(7, 1, 2, 3, 4, 5, 6)
val newListBuffer4: ListBuffer[Int] = listBuffer ++ ListBuffer[Int](7, 8) // ListBuffer(7, 1, 2, 3, 4, 5, 6, 7, 8)
listBuffer ++= ListBuffer[Int](7)
// 插入数据
listBuffer.insert(1, 0, 0) // ListBuffer(1, 0, 0, 2, 3, 4, 5, 6, 7)
// 修改数据
listBuffer(1) = 9 // ListBuffer(1, 9, 0, 2, 3, 4, 5, 6, 7)
val newLlistBuffer5: ListBuffer[Int] = listBuffer.updated(2, 9) // ListBuffer(1, 9, 9, 2, 3, 4, 5, 6, 7)
// 删除数据
listBuffer.remove(1, 2) // ListBuffer(1, 2, 3, 4, 5, 6, 7)
listBuffer.remove(6) // ListBuffer(1, 2, 3, 4, 5, 6)
val newListBuffer6: ListBuffer[Int] = listBuffer - 6 // ListBuffer(1, 2, 3, 4, 5)
listBuffer -= 6 // ListBuffer(1, 2, 3, 4, 5)
// 遍历集合
listBuffer.foreach(println)
println(listBuffer.mkString(", "))
// 判断集合是否为空
println(listBuffer.isEmpty) // false
// 可变集合 => 不可变集合
val list1: List[Int] = listBuffer.toList
// 不可变集合 => 可变集合
val list2: mutable.Buffer[Int] = list1.toBuffer
Set集合
不可变Set
// 默认的集合是不可变集合
// 数据是<无序, 不可重复>的
val set: Set[Int] = Set(1, 2, 3, 4 , 1) // Set(1, 2, 3, 4)
// 访问数据
println(set(1)) // true
// 增加数据
val newSet1: Set[Int] = set + 5 // Set(5, 1, 2, 3, 4)
val newSet2: Set[Int] = set ++ Set[Int](5, 6) // Set(5, 1, 6, 2, 3, 4)
// 删除数据
val newSet3: Set[Int] = set - 1 // Set(2, 3, 4)
val newSet4: Set[Int] = set -- Set[Int](1, 2) // Set(3, 4)
// 遍历数据
set.foreach(println)
println(set.mkString(", "))
// 判断集合是否为空
println(set.isEmpty) // false
可变Set
// 数据是<无序, 不可重复>的
// 创建集合
val set: mutable.Set[Int] = mutable.Set(1, 2, 3, 4, 1) // Set(1, 2, 3, 4)
// 访问数据
println(set(1)) // true
// 增加数据
set.add(5) // Set(1, 5, 2, 3, 4)
val newSet1: mutable.Set[Int] = set + 6 // Set(1, 5, 2, 6, 3, 4)
set += 6 // Set(1, 5, 2, 6, 3, 4)
// 修改数据
// 添加
set.update(7, true) // Set(1, 5, 2, 6, 3, 7, 4)
// 删除
set.update(5, false) // Set(1, 2, 6, 3, 7, 4)
// 删除数据
val newSet2: mutable.Set[Int] = set - 7 - 4 // Set(1, 2, 6, 3)
set -= 7 // Set(1, 2, 6, 3, 4)
// 遍历数据
println(set.mkString(", "))
set.foreach(println)
// 判断集合是否为空
println(set.isEmpty) // false
val set1: mutable.Set[Int] = mutable.Set(1, 2, 3, 4)
val set2: mutable.Set[Int] = mutable.Set(3, 4, 5, 6)
// 交集
val newSet3: mutable.Set[Int] = set1 & set2 // Set(3, 4)
// 差集
val newSet4: mutable.Set[Int] = set1 &~ set2 // Set(1, 2)
Map集合
不可变Map
// 数据是<无序, key不可重复(覆盖)>的
// 创建集合
val map: Map[String, Int] = Map("a" -> 6, "b" -> 2, "c" -> 3, "d" -> 4, "e" -> 5, "a" -> 1)
// 访问数据
// 获取指定key的值
val i1: Int = map("a") // 1
val i2: Int = map.apply("b") // 2
// 获取可能存在的值
val maybeInt1: Option[Int] = map.get("c") // Some(3)
val maybeInt2: Option[Int] = map.get("z") // None
// 获取可能存在的值。 若存在, 则返回该值 ; 若不存在, 则返回默认值(此处为-1)
val i3: Int = map.get("z").getOrElse(-1) // -1
val i4: Int = map.getOrElse("z", -1) // -1
// 增加数据
val newMap1: Map[String, Int] = map + ("e" -> 6) // Map(e -> 6, a -> 1, b -> 2, c -> 3, d -> 4)
val newMap2: Map[String, Int] = map ++ Map("d" -> 5, "e" -> 4) // Map(e -> 4, a -> 1, b -> 2, c -> 3, d -> 5)
// 修改数据
val newMap3: Map[String, Int] = map.updated("e", 100) // Map(e -> 100, a -> 1, b -> 2, c -> 3, d -> 4)
// 删除数据
val newMap4: Map[String, Int] = map - "e" - "d" // Map(a -> 1, b -> 2, c -> 3)
// 遍历集合
map.foreach(println)
println(map.mkString(", "))
// 判断集合是否为空
println(map.isEmpty) // false
// 创建空集合
val empty: Map[Nothing, Nothing] = Map.empty
可变Map
// 数据是<无序, key不可重复(覆盖)>的
val map: mutable.Map[String, Int] = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
// 访问数据(与不可变Map方式相同)
// 添加数据
val newMap1: mutable.Map[String, Int] = map + ("d" -> 4) // Map(b -> 2, d -> 4, a -> 1, c -> 3)
map += ("d" -> 4) // Map(b -> 2, d -> 4, a -> 1, c -> 3)
val newMap2: mutable.Map[String, Int] = map ++ mutable.Map("d" -> 4, "e" -> 5) // Map(e -> 5, b -> 2, d -> 4, a -> 1, c -> 3)
map ++= mutable.Map("e" -> 5, "f" -> 6) // Map(e -> 5, b -> 2, d -> 4, a -> 1, c -> 3, f -> 6)
// 修改数据
map.update("e", 8) // Map(e -> 8, b -> 2, d -> 4, a -> 1, c -> 3, f -> 6)
// 删除数据
map.remove("e") // Map(b -> 2, d -> 4, a -> 1, c -> 3, f -> 6)
val newMap3: mutable.Map[String, Int] = map - "d" // Map(b -> 2, a -> 1, c -> 3, f -> 6)
map -= "d" // Map(b -> 2, a -> 1, c -> 3, f -> 6)
// map.clear() // Map()
// 遍历数据
map.foreach(println)
println(map.mkString(", "))
val seq: Seq[(String, Int)] = map.toSeq
val array: Array[(String, Int)] = map.toArray
val set: Set[(String, Int)] = map.toSet
val list: List[(String, Int)] = map.toList
val map1: Map[String, Int] = seq.toMap
val map2: Map[String, Int] = array.toMap
val map3: Map[String, Int] = set.toMap
val map4: Map[String, Int] = list.toMap
Tuple(元组)
// Scala中, 我们可以将多个无关的数据元素封装为一个整体, 这个整体称之为元组。
// 换言之, 元组就是容纳元素的容器, 其中最多只能容纳22个元素, 但元素类型无限制
// 创建元组
val tuple: (Int, String, Int) = (1, "zhang san", 30)
// 访问数据
// 根据顺序访问
println(tuple._1) // 1
println(tuple._2) // zhang san
println(tuple._3) // 30
// 根据索引访问
println(tuple.productElement(0)) // 1
println(tuple.productElement(1)) // zhang san
println(tuple.productElement(2)) // 30
// 迭代器访问
val iterator: Iterator[Any] = tuple.productIterator
while (iterator.hasNext) {
println(iterator.next())
}
// 获取其长度
println(tuple.productPrefix) // Tuple3
// 当元组中的元素只有两个时, 我们称之为<对偶元组>, 也称之为<键值对>
val kv1: (String, Int) = ("a", 1)
val kv2: (String, Int) = "a" -> 1
// Map中的元素其实就是<元组>
val map: Map[String, Int] = Map(("a", 1), ("b", 2), ("c", 3))
for (elem <- map) {
println(elem._1 + "=" + elem._2)
}
集合常用方法
val list = List(1, 2, 3, 4)
val list2 = List(3, 4, 5, 6)
// 获取集合的长度
println(list.length) // 4
println(list.size) // 4
// 遍历数据
println(list.mkString(", "))
list.foreach(println)
val iterator: Iterator[Int] = list.iterator
while(iterator.hasNext){
println(iterator.next())
}
// 判断是否为空
println(list.isEmpty) // false
// 简单运算
println(list.sum) // 10
println(list.product) // 24
println(list.min) // 1
println(list.max) // 4
// 函数式编程中使用最多的算法就是递归算法
// 头 : 第一个元素
val head: Int = list.head // 1
// 尾 : 除第一个之外的元素
val tail: List[Int] = list.tail // List(2, 3, 4)
// 尾迭代
val tails: Iterator[List[Int]] = list.tails
// 最后一个元素
val i1: Int = list.last // 4
// 初始化 : 除最后一个之外的元素
val ints1: List[Int] = list.init // List(1, 2, 3)
// 初始化迭代
val inits: Iterator[List[Int]] = list.inits
// 反转集合
val ints2: List[Int] = list.reverse // List(4, 3, 2, 1)
// 判断集合汇总是否包含某个元素
val bool: Boolean = list.contains(2) // true
// 去重
val ints3: List[Int] = list.distinct // List(1, 2, 3, 4)
val ints4: List[Int] = list.toSet.toList // List(1, 2, 3, 4)
// 取数据
val ints5: List[Int] = list.take(1) // List(1)
val ints6: List[Int] = list.takeRight(1) // List(4)
// 丢弃数据
val ints7: List[Int] = list.drop(1) // List(2, 3, 4)
val ints8: List[Int] = list.dropRight(1) // List(1, 2, 3)
// 集合并集
val ints9: List[Int] = list.union(list2) // List(1, 2, 3, 4, 3, 4, 5, 6)
// 集合交集
val ints10: List[Int] = list.intersect(list2) // List(3, 4)
// 集合差集
val ints11: List[Int] = list.diff(list2) // List(1, 2)
// 切分集合
val tuple: (List[Int], List[Int]) = list.splitAt(2) // (List(1, 2),List(3, 4))
// 滑动
val iterator1: Iterator[List[Int]] = list.sliding(2)
while(iterator1.hasNext){
println(iterator1.next())
}
// 滚动
val iterator2: Iterator[List[Int]] = list.sliding(2, 2)
while(iterator2.hasNext){
println(iterator2.next())
}
// 拉链
val tuples: List[(Int, Int)] = list.zip(list2) // List((1,3), (2,4), (3,5), (4,6))
// 数据索引拉链
val index: List[(Int, Int)] = list.zipWithIndex // List((1,0), (2,1), (3,2), (4,3))
//============= 映射转换(map) ===============
val list1 = List(1, 2, 3, 4, 5, 6)
// 需求 : 将list1集合中所有元素 * 2 倍
// 方法一 : yield
val newList1: List[Int] = for (elem <- list1) yield {
elem * 2
}
// 方法二 : 映射转换(map)
// map方法可以将集合通过制定的转换规则变成新的集合
// 制定的转换规则适用于集合中的每一个元素
val newList2: List[Int] = list1.map(_ * 2)
// 在kv数据处理过程中, 如果k保持不变, 只对v进行处理, 可以采用mapValues()方法
val map = Map(("a", 1), ("b", 2), ("c", 3))
map.mapValues(_ * 2) // Map(a -> 2, b -> 4, c -> 6)
//============== 扁平化(flatten) ==============
// 所谓的扁平化, 就是将整体拆分成个体使用
// 扁平化操作默认只能对外层数据进行操作, 内层的数据无法操作
val list2 = List(List(List(1, 2)), List(List(3, 4)))
// 需求 : 获取list2集合中的所有数字
// 1::list:::Nil
val newList3: List[List[Int]] = list2.flatten // List(List(1, 2), List(3, 4))
val newList4: List[Int] = list2.flatten.flatten // List(1, 2, 3, 4)
//============== 扁平化映射(flatMap) ==============
val list3 = List("hello scala", "hello spark")
// 需求 : 获取list3集合中所有的单词
// 方法一 : 扁平化 -> 映射转换
val newList5: List[List[String]] = list3.map(_.split(" ").toList) // List(List(hello, scala), List(hello, spark))
val newList6: List[String] = newList5.flatten // List(hello, scala, hello, spark)
// 方法二 : 扁平化 + 映射转换
// 方法中的参数
val newList7: List[String] = list3.flatMap(_.split(" ").toList) // List(hello, scala, hello, spark)
// 从原理上来讲, 做不了扁平化, 但是逻辑上可以实现
val list4 = List(1, 2, 3, 4)
println(list4.flatMap(num => List(num * 2))) // List(2, 4, 6, 8)
object Method_3 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
//============== 过滤 ==============
val newList1: List[Int] = list.filter(_ % 2 == 0) // List(2, 4)
//============== 分组 ==============
val newList2: Map[Int, List[Int]] = list.groupBy(_ % 2) // Map(1 -> List(1, 3), 0 -> List(2, 4))
//============== 排序 ==============
// 默认升序
val newList3: List[Int] = list.sortBy(word => word) // List(1, 2, 3, 4)
// 降序操作
val newList4: List[Int] = list.sortBy(word => -word) // List(4, 3, 2, 1)
val newList5: List[Int] = list.sortBy(word => word)(Ordering.Int.reverse) // List(4, 3, 2, 1)
// 自定义规则排序
val list4 = List("1", "5", "3", "4")
val newList: List[String] = list4.sortWith((left, right) => (left.toInt - right.toInt) > 0) // List(5, 4, 3, 1)
// 需求 : 对User对象进行排序 : 先按照年龄升序, 再按照名字降序
val user1 = new User()
user1.name = "wangwu"
user1.age = 30
val user2 = new User()
user2.name = "lisi"
user2.age = 20
val user3 = new User()
user3.name = "zhangsan"
user3.age = 20
val users = List(user1, user2, user3)
// Tuple自动比较大小
// 规则 : 先比较第一个元素, 再比较第二个元素, 以此类推...默认升序
val resultList: List[User] = users.sortBy(user =>
(-user.age, user.name))(Ordering.Tuple2[Int, String].reverse) // List(<zhangsan,20>, <lisi,20>, <wangwu,30>)
}
}
class User {
var name: String = _
var age: Int = _
override def toString: String = "<" + name + "," + age + ">"
}
val list1 = List(1, 2, 3, 4)
val list2 = List(3, 4, 5, 6)
// 拉链
// 两个集合的相同位置的数据进行关联
val tuples: List[(Int, Int)] = list1.zip(list2) // List((1,3), (2,4), (3,5), (4,6))
val list3 = List(5, 4, 3, 2, 1)
// 不同长度的List拉链
val tuples1: List[(Int, Int)] = list1.zip(list3) // List((1,5), (2,4), (3,3), (4,2))
// 自拉链
val tuples2: List[(Int, Int)] = list3.zip(list3) // List((5,5), (4,4), (3,3), (2,2), (1,1))
// 数据索引拉链
val index: List[(Int, Int)] = list1.zipWithIndex // List((1,0), (2,1), (3,2), (4,3))
// 滑动
// 数据指定的范围进行滑动, 这个范围称之为窗口
val iterator1: Iterator[List[Int]] = list1.sliding(2) // 每个窗口两条数据, 步长默认为1
while(iterator1.hasNext){
print(iterator1.next() + " ") // List(1, 2) List(2, 3) List(3, 4) List(1, 2)
}
val iterator2: Iterator[List[Int]] = list1.sliding(2, 3) // 每个窗口两条数据, 步长为3
while(iterator2.hasNext){
print(iterator2.next() + " ") // List(1, 2) List(4)
}
// 自定义规则排序
val list4 = List("1", "5", "3", "4")
val newList: List[String] = list4.sortWith((left, right) => (left.toInt - right.toInt) > 0) // List(5, 4, 3, 1)
// ============ Reduce(简化规约) ============
// reduce中传递的参数的规则 : 参数和返回值类型相同
// Scala中的集合的计算基本都是两两计算
val list = List(1, 2, 3, 4)
val i1: Int = list.reduce(_ - _) // -8
// 原理 : (((1 - 2) - 3) - 4) = 10
// 源码中, reduce操作就是reduceLeft
val i2: Int = list.reduceLeft(_ - _) // -8
val i3: Int = list.reduceRight(_ - _) // -2
// 原理 : (1 - (2 - (3 - 4))) = -2
// ================ 折叠 =================
// 将集合之外的数据和集合内的数据进行聚合操作
// fold的参数 : [(z : A1)((A1, A1) => A1)] >> z为zero, 表示数据处理的初始值
// fold方法在进行数据处理时, 外部的数据应该和内部的数据的类型保持一致
val i4: Int = list.fold(10)(_ - _) // 0
// 源码中, fold的操作就是foldLeft
// fold, foldLeft, foldRight方法的返回值类型为初始值类型
val str1: String = list.foldLeft("a")(_ + _) // a1234
// 原理 : (((("a" + 1) + 2) + 3) + 4)
val str2: String = list.foldRight("a")(_ + _) // 1234a
// 原理 : (1 + (2 + (3 + (4 + "a"))))
// 需求 : 合并集合(聚合)
// Map("a" -> 1, "b" -> 2, "c" -> 3)
// Map("a" -> 4, "d" -> 5, "c" -> 6)
// ===> Map(a -> 5, b -> 2, d -> 5, c -> 9)
val map1: mutable.Map[String, Int] = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
val map2: mutable.Map[String, Int] = mutable.Map("a" -> 4, "d" -> 5, "c" -> 6)
val newMap: Map[String, Int] = map1.foldLeft(map2)((map, kv) => {
map(kv._1) = map.getOrElse(kv._1, 0) + kv._2
map
}).
toList.
sortBy(_._1).
toMap
println(newMap) // Map(a -> 5, b -> 2, c -> 9, d -> 5)
// ==================== 扫描 =======================
// fold方法直接获取最终的结果
// scan方法类似fold, 但是会将中间的处理结果也保留
val newList1: List[Int] = list.scan(10)(_-_) // List(10, 9, 7, 4, 0)
val newList2: List[String] = list.scanLeft("a")(_+_) // List(a, a1, a12, a123, a1234)
val newList3: List[String] = list.scanRight("a")(_+_) // List(1234a, 234a, 34a, 4a, a)
// =================== 队列(Queue) =========================
// 创建队列
val que = new mutable.Queue[String]()
// 添加元素(进队)
que.enqueue("a", "b", "c") // Queue(a, b, c)
que += "d" // Queue(a, b, c, d)
// 获取元素(出队)
// 先进先出
que.dequeue() // Queue(b, c, d)
que.dequeue() // Queue(c, d)
que.dequeue() // Queue(d)
// =================== 并行 ===================
// Scala为了充分使用多核CPU, 提供了并行集合, 用于多核环境的并行计算
// Vector(串行)
val result1 = (0 to 100).map{x => Thread.currentThread.getName}
// ParVector(并行)
val result2 = (0 to 100).par.map{x => Thread.currentThread.getName}
println(result1)
println(result2)
案例实操
需求 : 将文件中的单词出现的次数统计并排序取前三名
输入数据 :
目录 : 当前Project\input\WordCount.txt
Scala Hello Spark
Spark Test Java Test
Scala Word Scala Test
Spark Stock Word Sink Source
From Test Scala
输出数据 :
(Scala,4)
(Test,4)
(Spark,3)
代码实现 :
// 1. 从文件中按行获取数据
val lineList: List[String] = Source.fromFile("input/WordCount.txt").getLines().toList
// 2. 将每行数据切分为单词
val wordList: List[String] = lineList.flatMap(_.split(" "))
// 3. 按单词进行分组
val wordGroupMap: Map[String, List[String]] = wordList.groupBy(word => word)
// 4. 对单词出现的次数进行统计
val wordToCountMap: Map[String, Int] = wordGroupMap.map(kv => (kv._1, kv._2.length))
// 5. 按照v进行降序
val sortList: List[(String, Int)] = wordToCountMap.toList.sortBy(_._2)(Ordering.Int.reverse)
// 6. 取Top3
val result: List[(String, Int)] = sortList.take(3)
// 输出
result.foreach(println)
简化后代码 :
Source.fromFile("input/WordCount.txt").
getLines().
toList.
flatMap(line => line.split(" ")).
groupBy(word => word).
map(kv => (kv._1, kv._2.length)).
toList.
sortBy(_._2)(Ordering.Int.reverse).
take(3).foreach(println)
需求 :1. 将targetList中的数据进行WordCount后排序取前三名。
val targetList = List(
(“hello”, 4),
(“hello spark”, 3),
(“hello spark scala”, 2),
(“hello spark scala hive”, 1)
)
输出数据 :
(hello,10)
(spark,6)
(scala,3)
实现方式一 :
// 获取单词
val lineList: List[String] = targetList.map(k => (k._1 + " ") * k._2)
val wordList: List[String] = lineList.flatMap(line => line.split(" "))
// 分组
val wordGroupMap: Map[String, List[String]] = wordList.groupBy(word => word)
// 统计
val resultMap: Map[String, Int] = wordGroupMap.map(kv => (kv._1, kv._2.length))
// 降序
val resultSortList: List[(String, Int)] = resultMap.toList.sortBy(_._2)(Ordering.Int.reverse)
// 取Top3
val resultList: List[(String, Int)] = resultSortList.take(3)
// 打印
println(resultList.mkString("\n"))
实现方式一简化后代码 :
println(targetList.map(k => (k._1 + " ") * k._2).
flatMap(a => a.split(" ")).
groupBy(word => word).
map(kv => (kv._1, kv._2.length)).
toList.
sortBy(kv => kv._2)(Ordering.Int.reverse).
take(3).
mkString("\n"))
实现方式二 :
def main(args: Array[String]): Unit = {
val targetList = List(
("hello", 4),
("hello spark", 3),
("hello spark scala", 2),
("hello spark scala hive", 1)
)
// 需求 :
// 1. 将上面的数据进行WordCount后排序取前三名!
// 2. 使用2种不同的方式。
// 结果一 :
// ("hello", 4)
// ("hello", 3), ("spark", 3)
// ("hello", 2), ("spark", 2), ("scala", 2)
// ("hello", 1), ("spark", 1), ("scala", 1), ("hive", 1)
println(targetList.
flatMap(kv => kv._1.split(" ").map(word => (word, kv._2))). // 获取结果一
groupBy(_._1). // 根据word分组 Map[String, List[Tuple[String, Int]]]
map(kv => (kv._1, kv._2.map(_._2).sum)). // 获取word出现的次数count Map[String, Int]
toList.sortBy(_._2). // 根据count排序 Map[String, Int]
take(3). // 取Top3
mkString("\n"))
}
模式匹配
概述
Scala中的模式匹配类似于Java中的switch语法,但是scala从语法中补充了更多的功能,可以按照指定的规则对数据或对象进行匹配, 所以更加强大。
int i = 20
switch (i) {
default :
System.out.println("other number");
break;
case 10 :
System.out.println("10");
//break;
case 20 :
System.out.println("20");
break;
}
匹配规则
//============== 匹配常量(Constant) ===================
def describe1(x: Any): String = x match {
case 5 => "Int five"
case "hello" => "String hello"
case true => "Boolean true"
case '+' => "Char +"
case _ => "Other thing"
}
println(describe1(5)) // Int five
//============== 匹配类型(Type) ====================
def describe2(x: Any) = x match {
case i: Int => "Int"
case s: String => "String"
case l: List[_] => "List"
case a: Array[_] => "Array"
case otherThing => "otherThing else" + otherThing
}
println(describe2(a)) // Int
//============== 匹配数组(Array) ==================
val array1 = Array(
Array(0),
Array(1, 0),
Array(0, 1, 0),
Array(1, 1, 0),
Array(1, 1, 0, 1),
Array("hello", 90))
for (arr <- array1) {
val result = arr match {
case Array(0) => "0"
case Array(x, y) => x + "," + y
case Array(0, _*) => "以0开头的数组"
case _ => "something else"
}
println("result = " + result)
}
//============= 匹配列表(List) ===============
val array2 = Array(List(0), List(1, 0), List(0, 0, 0), List(8, 8))
for (arr <- array2) {
val result = arr match {
case List(0) => "0"
case List(x, y) => x + "," + y
case List(0, _*) => "以0开头"
case _ => "something else"
}
println("result = " + result)
}
//============== 匹配元组(Tuple) =================
val array3 = Array((0, 1), (1, 0), (1, 1), (1, 0, 2))
for (arr <- array3) {
val result = arr match {
case (0, 0) => "(0, 0)"
case (0, _) => "(0, *)"
case (x, 0) => "(*, 0)"
case (x, y) => s"($x, $y)"
case _ => "something else"
}
println(result)
}
//============== 匹配对象 ==================
class User1(var name: String, var age: Int)
object User1 {
// 使用参数自动构建对象
def apply(name: String, age: Int): User1 = new User1(name, age)
// 使用对象自动获取参数
def unapply(user: User1): Option[(String, Int)] = {
Option((user.name, user.age))
}
}
val user1 = User1("zhang san", 11)
// Scala中模式匹配对象时, 会自动调用对象的unapply方法进行匹配
def result2 = user1 match {
case User1("zhang san", 11) => "yes"
case _ => "no"
}
println(result2)
// ============= 样例类 ===============
// 使用case关键字声明的类, 称之为样例类
// 专门用于匹配对象
// 1. 样例类在编译时, 会自动生成伴生类以及一些常用方法
// 如 : apply、unapply、toString、equals、hashCode、copy
// 2. 样例类的构造参数默认使用val声明, 所以参数其实就是类的属性
// 如果想要更改属性, 需要显示的将属性使用var声明(不建议这样做)
// 3. 样例类自动实现可序列化接口
// 4. 在实际开发中, 一般都使用样例, 便于开发
case class User2(name:String, age:Int)
val user2:User2 = User2("zhang san", 23)
val result3 = user2 match{
case User2("zhang san", 23) => "yes"
case _ => "no"
}
println(result3)
应用场景
// 变量声明 (元组)
val (id, name, age) = (1, "zhang san", 24)
println(age) // 24
val Array(first, second, _*) = Array(1, 7, 2, 9)
println(first) // 1
// 循环匹配
val map1: Map[String, Int] = Map("A" -> 1, "B" -> 2, "C" -> 3)
for ((k, v) <- map1) { // 直接将map2中的kv匹配到
println(k + " -> " + v)
}
for ((k, 2) <- map1) { // 匹配出v为0的k
println(k + " -> " + 2)
}
for ((k, v) <- map1 if v == 2) { // 匹配出v为2的kv
println(k + " -> " + v)
}
// 函数参数
val map2: Map[String, (String, Int)] = Map("张三" -> ("男", 24), "李四" -> ("男", 26))
map2.foreach { case (name, (sex, age)) => println(age) } // 此处需使用花括号
val list1 = List(("a", 1), ("b", 2), ("c", 3))
val newList1: List[(String, Int)] = list1.map { case (word, count) => (word, count * 2) } // 此处需使用花括号
println(newList1) // List((a,2), (b,4), (c,6))
偏函数
// 偏函数
// 就是对集合中符合条件的数据进行处理的函数
// 声明偏函数
val pf: PartialFunction[Any, Any] = {case i: Int => i} // 只匹配Int的元素
// 应用偏函数
val list2 = List(1, 2, "3", 4)
// println(list2.map(pf)) // map()方法不支持偏函数, 报 MatchError
println(list2.collect(pf)) // List(1, 2, 4)
//=========== 案例实操 ============
// 需求 : 将list3中的Int类型的元素加一, 并去掉字符串
val list3 = List(1, 2, 3, 4, 5, 6, "7")
println(list3.collect { case i: Int => i + 1 }) // List(2, 3, 4, 5, 6, 7)
异常
//=========== 异常 - Java =============
// 异常的处理顺序应上小下大, 否则会报编译时异常
try {
// 可能发生异常的代码
} catch (ArithmeticException ae) {
// 当发生算术异常时的备用方案
} catch (Exception e) {
// 当发生非算术异常的异常时的备用方案
} finally {
// 不管是否发生异常, 一定会被执行的的代码
// 常用于资源的释放
}
//=========== 异常 - Scala =============
// 处理顺序无要求,不报错, 执行时顺序执行。一般上小下大
// Scala中不区分编译时异常和运行时异常, 也无需显示抛出异常, 所以Scala中没有throws关键字
try {
// 可能发生异常的代码
} catch {
case ae: ArithmeticException => {
// 发生算术异常时的备用方案
}
case e: Exception => {
// 发生非算术异常的异常时的备用方案
}
} finally {
// 不管是否发生异常, 一定会被执行的代码
// 常用于资源的释放
}
隐式转换
//=============== 隐式转换 ===============
// 应用场景 :
// 1. 当程序因意外情况, 导致正确的逻辑出现错误。
// 2. 扩展功能。
// 为了遵循OCP原则, 可以使用隐式转换的方式对代码进行"修改/升级"
//=============== 隐式函数 ===============
// 声明隐式函数
implicit def doubleToInt1(d: Double): Int = {
d.toInt
}
val d1: Double = 2.0
val i1: Int = d1 // Double => Int 正常情况下会报错
// 当编译不通过时, 编译器会查找相匹配的自定义隐式函数, 并进行二次编译(隐式调用)
println(i1) // 2
//============ 隐式参数 & 隐式变量 ===============
def doubleToInt2(implicit d: Double): Int = {
d.toInt
}
implicit val d2: Double = 2.0
println(doubleToInt2) // 2
val emp : Emp = new Emp
emp.insertFun()
// 使用隐式类的功能
emp.updataFun()
// 隐式机制
// 当编译出现错误时, 编译器会从哪些地方查找对应的隐式转换规则
// 1. 当前代码作用域
// 2. 当前代码上级作用域
// 3. 当前类所在的包对象
// 4. 当前类(对象)的父类或特质(父特质)
}
//============= 隐式类 ================
// 在Scala后提供了隐式类的概念
// 要求 : 1. 构造参数有且只能有一个 ; 2. 隐式类不能是顶级的, 必须被定义在类/伴生对象/包对象中
class Emp {
def insertFun(): Unit = {
println("insertFun...")
}
}
implicit class User(emp: Emp) {
def updataFun(): Unit = {
println("updataFun...")
}
}
泛型
Scala的泛型和Java中的泛型表达的含义都是一样的,对处理的数据类型进行约束,但是Scala提供了更加强大的功能
class Test[A] {
private var elements: List[A] = Nil
}
泛型转换
泛型不可变
object ScalaGeneric {
def main(args: Array[String]): Unit = {
val test1 : Test[User] = new Test[User] // OK
val test2 : Test[User] = new Test[Parent] // Error
val test3 : Test[User] = new Test[SubUser] // Error
}
class Test[T] {
}
class Parent {
}
class User extends Parent{
}
class SubUser extends User {
}
}
泛型协变
object ScalaGeneric {
def main(args: Array[String]): Unit = {
val test1 : Test[User] = new Test[User] // OK
val test2 : Test[User] = new Test[Parent] // Error
val test3 : Test[User] = new Test[SubUser] // OK
}
class Test[+T] {
}
class Parent {
}
class User extends Parent{
}
class SubUser extends User {
}
}
泛型逆变
object ScalaGeneric {
def main(args: Array[String]): Unit = {
val test1 : Test[User] = new Test[User] // OK
val test2 : Test[User] = new Test[Parent] // OK
val test3 : Test[User] = new Test[SubUser] // Error
}
class Test[-T] {
}
class Parent {
}
class User extends Parent{
}
class SubUser extends User {
}
}
泛型边界
Scala的泛型可以根据功能设定类树的边界
object ScalaGeneric {
def main(args: Array[String]): Unit = {
val parent : Parent = new Parent()
val user : User = new User()
val subuser : SubUser = new SubUser()
test[User](parent) // Error
test[User](user) // OK
test[User](subuser) // OK
}
def test[A]( a : A ): Unit = {
println(a)
}
class Parent {
}
class User extends Parent{
}
class SubUser extends User {
}
}
泛型上限
object ScalaGeneric {
def main(args: Array[String]): Unit = {
val parent : Parent = new Parent()
val user : User = new User()
val subuser : SubUser = new SubUser()
test[Parent](parent) // Error
test[User](user) // OK
test[SubUser](subuser) // OK
}
def test[A<:User]( a : A ): Unit = {
println(a)
}
class Parent {
}
class User extends Parent{
}
class SubUser extends User {
}
}
泛型下限
object ScalaGeneric {
def main(args: Array[String]): Unit = {
val parent : Parent = new Parent()
val user : User = new User()
val subuser : SubUser = new SubUser()
test[Parent](parent) // OK
test[User](user) // OK
test[SubUser](subuser) // Error
}
def test[A>:User]( a : A ): Unit = {
println(a)
}
class Parent {
}
class User extends Parent{
}
class SubUser extends User {
}
}
上下文限定
上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过implicitly[Ordering[A]]获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误。
object ScalaGeneric {
def main(args: Array[String]): Unit = {
def f[A : Test](a: A) = println(a)
implicit val test : Test[User] = new Test[User]
f( new User() )
}
class Test[T] {
}
class Parent {
}
class User extends Parent{
}
class SubUser extends User {
}
}
正则表达式
简介
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
语法
object ScalaRegex {
def main(args: Array[String]): Unit = {
// 构建正则表达式
val pattern = "Scala".r
val str = "Scala is Scalable Language"
// 匹配字符串 - 第一个
println(pattern findFirstIn str)
// 匹配字符串 - 所有
val iterator: Regex.MatchIterator = pattern findAllIn str
while ( iterator.hasNext ) {
println(iterator.next())
}
println("***************************")
// 匹配规则:大写,小写都可
val pattern1 = new Regex("(S|s)cala")
val str1 = "Scala is scalable Language"
println((pattern1 findAllIn str1).mkString(","))
}
}
案例实操
手机号正则表达式验证方法
object ScalaRegex {
def main(args: Array[String]): Unit = {
// 构建正则表达式
println(isMobileNumber("18801234567"))
println(isMobileNumber("11111111111"))
}
def isMobileNumber(number: String): Boolean ={
val regex = "^((13[0-9])|(14[5,7,9])|(15[^4])|(18[0-9])|(17[0,1,3,5,6,7,8]))[0-9]{8}$".r
val length = number.length
regex.findFirstMatchIn(number.slice(length-11,length)) != None
}
}
提取邮件地址的域名部分
object ScalaRegex {
def main(args: Array[String]): Unit = {
// 构建正则表达式
val r = """([_A-Za-z0-9-]+(?:\.[_A-Za-z0-9-\+]+)*)(@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*(?:\.[A-Za-z]{2,})) ?""".r
println(r.replaceAllIn("abc.edf+jianli@gmail.com hello@gmail.com.cn", (m => "*****" + m.group(2))))
}
}
下划线的用法总结
- 下划线可以作为标识符使用
- 下划线可以将函数作为整体使用
- 下划线在匿名函数中可以代替参数使用
- 下划线在导类时可以代替java中的*
- 下划线可以将制定类在编译时隐藏
- 下划线可以对类的属性进行默认初始化
- 如果匿名函数的参数不采纳与逻辑处理, 可以使用下划线省略
- 下划线在模式匹配中可以表示所有的值(default)