Scala
Scala 和 java 关系
语言特点
Scala是一门以Java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言(静态语言需要提前编译的如:Java、c、c++等,动态语言如:js)
Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程。(多范式,就是多种编程方法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法。)
环境
安装对应Java版本的scala 解压 配置系统环境变量
scala 插件安装
默认情况下 IDEA 不支持 Scala 的开发,需要安装 Scala 插件。可在线安装
默认下,Maven 不支持 Scala 的开发,需要引入 Scala 框架。
helloworld 例子 不用分号
scala 面向对象 object 伴生对象 不是类 会使用$.class 单例方式生成对象 采用伴生对象单例的方式调用方法
HelloWorld.class
HelloWorld$.class
/*
object: 关键字,声明一个单例对象(伴生对象)
*/
object HelloWorld {
/*
main 方法:从外部可以直接调用执行的方法
def 方法名称(参数名称: 参数类型): 返回值类型 = { 方法体 }
*/
def main(args: Array[String]): Unit = {
println("hello world")
System.out.println("hello scala from java")
}
}
语法分析
main 方法名 scala中声明方法必须采用关键字def声明
小括号 表示参数列表
参数声明方式:java->类型 参数名 scala->参数名 : 类型
方法声明:scala 方法名(参数列表):返回值类型
scala中没有public 关键字,如果不声明访问权限,那么就是公共的
scala中没有静态方法,所以没有static关键字 由object实现类似静态方法的功能**(类名.方法名)**
scala中由 Unit 类型表示没有返回值
Scala 中方法 实现复制给方法声明,所以中间需要等号连接
class 关键字和java 中class关键字作用相同,用来定义一个类
反编译
变量和类型
代码规范
- ctrl + alt + L 来进行格式化
- 运算符两边习惯性各加一个空格。比如:2 + 4 * 5
- 一行最长不超过 80 个字符,超过的请使用换行展示,尽量保持格式优雅
class Student(name:String,age:Int){
def printInfo():Unit={
println(name+" "+age+" "+Student.school)
}
}
Object Student{
val school: String="atguigu"
}
变量 var 和常量 val 语法
常量:在程序执行的过程中,其值不会被改变的变量
- var 变量名 [: 变量类型] = 初始值 ——var i:Int = 10
- val 常量名 [: 常量类型] = 初始值 ——val j:Int = 20
注意:能用常量的地方不用变量
快捷键 main 出主方法 def
案例实操
- 声明变量,常量时 val,类型可以省略,编译器自动推导,即类型推导
- 类型确定后,就不能修改,说明 Scala 是强数据类型语言。
- 变量声明时,必须要有初始值
- 在声明/定义一个变量时,可以用 var 或者 val 来修饰,var 修饰变量可改变,val 修饰变量不可改。
- var 修饰的对象引用可以改变,val 修饰的对象则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)
object TestVar {
def main(args: Array[String]): Unit = {
// p1 是 var 修饰的,p1 的属性可以变,而且 p1 本身也可以变
var p1 = new Person()
p1.name = "dalang"
p1 = null
// p2 是 val 修饰的,那么 p2 本身就不可变(即 p2 的内存地址不能变),
//但是,p2 的属性是可以变,因为属性并没有用 val 修饰。
val p2 = new Person()
p2.name="jinlian"
// p2 = null // 错误的,因为 p2 是 val 修饰的
}
}
字符串输出
-
字符串,通过+号连接
-
printf 用法:字符串,通过**%传值。** 识别正则
-
字符串模板(插值字符串):通过 $ 获取变量值
// s"" 表示这是一个模板字符串
-
多行字符串,在 Scala 中,利用三个双引号包围多行字符串就可以实现。
输入的内容,带有空格、\t 之类,导致每一行的开始位置不能整洁对齐。
应用 scala 的 stripMargin 方法,在 scala 中 stripMargin 默认是 “|” 作为连接符,// 在多行换行的行头前面加一个 “|” 符号即可
object Test04_String {
def main(args: Array[String]): Unit = {
//(1)字符串,通过+号连接
val name: String = "alice"
val age: Int = 18
println(age + "岁的" + name + "在尚硅谷学习")
// *用于将一个字符串复制多次并拼接
println(name * 3)
//(2)printf用法:字符串,通过%传值。
printf("%d岁的%s在尚硅谷学习", age, name)
println()
//(3)字符串模板(插值字符串):通过$获取变量值
println(s"${age}岁的${name}在尚硅谷学习") // s直接填进来
val num: Double = 2.3456
println(f"The num is ${num}%2.2f") // 格式化模板字符串 format
println(raw"The num is ${num}%2.2f") // raw 原长
// 三引号表示字符串,保持多行字符串的原格式输出
val sql = s"""
|select *
|from
| student
|where
| name = ${name}
|and
| age > ${age}
|""".stripMargin
println(sql)
}
}
注意: printf 和 println 有区别 printf 能识别正则表达式
键盘输入 StdIn
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
object Test05_StdIn {
def main(args: Array[String]): Unit = {
// 输入信息
println("请输入您的大名:")
val name: String = StdIn.readLine()
println("请输入您的芳龄:")
val age: Int = StdIn.readInt()
// 控制台打印输出
println(s"欢迎${age}岁的${name}来到尚硅谷学习")
}
}
读写文件
scala.io包
写入数据 调用Java.io 包
object Test06_FileIO {
def main(args: Array[String]): Unit = {
// 1. 从文件中读取数据 函数式编程
Source.fromFile("src/main/resources/test.txt").foreach(print)
// 2. 将数据写入文件
val writer = new PrintWriter(new File("src/main/resources/output.txt"))
writer.write("hello scala from java writer")
writer.close()
}
}
数据类型
-
Scala中一切数据都是对象,都是Any的子类。
-
Scala中数据类型分为两大类:数值类型(AnyVal)、引用类型(AnyRef),不管是值类型还是引用类型都是对象。
-
Scala数据类型仍然遵守,低精度的值类型向高精度值类型,自动转换(隐式转换)
-
Scala中的StringOps是对Java中的String增强
-
Unit:对应Java中的void,用于方法返回值的位置,表示方法没有返回值。Unit是 一个数据类型,只有一个对象就是()。Void不是数据类型,只是一个关键字
-
Null是一个类型,只 有一个 象就 是null。它是所有引用类型(AnyRef)的子类。
-
Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,因为这样可以把抛出的返回值,返回给任何的变量或者函数。
数据类型 | 描述 |
---|---|
Byte [1] | 8 位有符号补码整数。数值区间为 -128 到 127 |
Short [2] | 16 位有符号补码整数。数值区间为 -32768 到 32767 |
Int [4] | 32 位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long [8] | 64 位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2 的(64-1)次方-1 |
Float [4] | 32 位, IEEE 754 标准的单精度浮点数 |
Double [8] | 64 位 IEEE 754 标准的双精度浮点数 |
\ " :表示 " 双引号
Unit Null 和 Nothing 类型(重点)
-
Unit (有返回)类型用来标识过程,也就是没有明确返回值的函数。由此可见,Unit 类似于 Java 里的 void。Unit 只有一个实例——( ),这个实例也没有实质意义
object TestSpecialType { def main(args: Array[String]): Unit = { def sayOk:Unit={} println(sayOk) } }
-
Null(有返回) 类只有一个实例对象,Null 类似于 Java 中的 null 引用。Null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
object TestDataType { def main(args: Array[String]): Unit = { //null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal) var cat = new Cat(); cat = null // 正确 var n1: Int = null // 错误 println("n1:" + n1) } } class Cat{ }
-
Nothing(不返回),可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于 Nothing 是其他任意类型的子类,他还能跟要求返回值的方法兼容。
object TestSpecialType { def main(args: Array[String]): Unit = { def test() : Nothing={ throw new Exception() } test } } object TestSpecialType { def test(n:Int):Int={ if(n==10) throw new Exception() else return n } def main(args: Array[String]): Unit = { println("a") test(10) } }
数值类型自动转换
当程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)
原则
- 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
- 把精度大的数值类型赋值给精度小的数值类型时,就会报错
- (byte,short)和 char 之间不会相互自动转换。
- byte,short,char 他们三者可以计算,在计算时首先转换为 int 类型。
注意:Scala 还提供了非常强大的隐式转换机制(隐式函数,隐式类等),我们放在高级部分专门用一个章节来讲解
强制类型转换
自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意
案例实操
- 将数据由高精度转换为低精度,就需要使用到强制转换
- 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
object TestForceTransfer {
def main(args: Array[String]): Unit = {
var n1:Int=2.5.toInt
// 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
var r1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt // 10 *3 + 6*1 = 36 降低了
var r2: Int = (10 * 3.5 + 6 * 1.5).toInt // 44.0.toInt =44
println("r1=" + r1 + " r2=" + r2)
}
}
数值类型和String 类型间转换
基本类型转 String 类型(语法:将基本类型的值+“” 即可)
String 类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
object Test08_DataTypeConversion {
def main(args: Array[String]): Unit = {
// 3. 数值类型和String类型的转换
// (1) 数值转String
val n: Int = 27
val s: String = n + ""
println(s)
// (2) String转数值
val m: Int = "12".toInt
val f: Float = "12.3".toFloat
val f2: Int = "12.3".toDouble.toInt
println(f2)
}
}
面试题
package chapter02
/*
128: Int类型,占据4个字节,32位
原码 0000 0000 0000 0000 0000 0000 1000 0000
补码 0000 0000 0000 0000 0000 0000 1000 0000
截取最后一个字节,Byte 8位
得到补码 1000 0000 第一个1 是符号位
表示最大负数 -128
130: Int类型,占据4个字节,32位
原码 0000 0000 0000 0000 0000 0000 1000 0010
补码 0000 0000 0000 0000 0000 0000 1000 0010
截取最后一个字节,Byte
得到补码 1000 0010 -128+2
对应原码 1111 1110 取反加一
-126
*/
object Test09_Problem_DataTypeConversion {
def main(args: Array[String]): Unit = {
var n: Int = 130
val b: Byte = n.toByte
println(b)
}
}
运算符
对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分
object Test01_TestOperator {
def main(args: Array[String]): Unit = {
val result1: Int = 10 / 3
println(result1) //3
val result2: Double = 10 / 3
println(result2) // 3.0
val result3: Double = 10.0 / 3 // 3.33
println(result3.formatted("%5.2f")) // 含义:保留小数点 2 位,使用四舍五入
}
}
Scala:==更加类似于 Java 中的 equals,参照 jd 工具 scala 里的string 就是java的string
eq // false 判断对象的引用地址
object Test01_TestOperator {
def main(args: Array[String]): Unit = {
val s1: String = "hello"
val s2: String = new String("hello")
println(s1 == s2) // true
println(s1.equals(s2)) // true
println(s1.eq(s2)) // false 判断对象的引用地址
}
}
Scala 中没有++、–操作符,可以通过+=、-=来实现同样的效果;
object TestAssignment {
def main(args: Array[String]): Unit = {
var r1 = 10
r1 += 1 // 没有++
r1 -= 2 // 没有--
} }
扩展避免逻辑与 && 空指针异常
object Test01_TestOperator {
def main(args: Array[String]): Unit = {
// 判断一个字符串是否为空 错误示范
def isNotEmpty(str: String): Boolean = {
return !(str=="") // 如果传进来的是null 不应该报错 应该false
}
// 判断一个字符串是否为空
def isNotEmpty(str: String): Boolean = {
return str != null && !("".equals(str.trim))
}
}
}
其他和Java运算符差不多
scala 运算符的本质 都是方法
在 Scala 中其实是没有运算符的,所有运算符都是方法。
- 当调用对象的方法时,点.可以省略
- 如果函数参数只有一个,或者没有参数,()可以省略
object TestOpt {
def main(args: Array[String]): Unit = {
// 标准的加法运算
val i:Int = 1.+(1)
// (1)当调用对象的方法时,.可以省略
val j:Int = 1 + (1)
// (2)如果函数参数只有一个,或者没有参数,()可以省略
val k:Int = 1 + 1
println(1.toString())
println(1 toString())
println(1 toString)
} }
流程控制
if else
需求:Scala 中 if else 表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age");
var age = StdIn.readInt();
val res: String = if (age < 18) {
"童年"
} else if (age >= 18 && age < 30) {
"中年"
} else {
"老年"
}
println(res)
}
}
需求:scala 中返回值类型不一致,取它们共同的祖先
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age");
var age = StdIn.readInt();
val res: Any = if (age < 18) {
"童年"
} else if (age >= 18 && age < 30) {
"中年"
} else {
100
}
println(res)
}
}
需求3:java中的三元运算符可以用if else实现
如果大括号{}内的逻辑代码只有一行,大括号可以省略。如果省略大括号,if 只对最近的一行逻辑代码起作用。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age");
var age = StdIn.readInt();
val res: Any = if (age < 18) "童年" else "成年"
// 不起作用
println(res)
}
}
switch 分支结构 scala 没有
在 Scala 中没有 Switch,而是使用模式匹配来处理。
模式匹配涉及到的知识点较为综合,因此我们放在后面讲解
for 循环控制
Scala 也为 for 循环这一常见的控制结构提供了非常多的特性,这些 for 循环的特性被称为 for 推导式或 for 表达式
范围数据循环 (To)
基本语法
i 表示循环的变量,<- 规定 to
i 将会从 1-5循环,前后闭合
object TestFor {
def main(args: Array[String]): Unit = {
for (i <- 1 to 5){
println("to")
}
}
}
until
这种方式和前面的区别在于 i 是从 1 到 5-1 =4 即前闭合后开的范围
object TestFor {
def main(args: Array[String]): Unit = {
for (i <- 1 until 5){
println("until")
}
}
}
循环守卫
循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为 true 则进入循环体内部,为 false 则跳过,类似于 continue
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
println()
等价于
for (i <- 1 to 3){
if (i != 2) {
print(i + " ")
} }
循环步长
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
嵌套循环
for(i <- 1 to 3; j <- 1 to 4 {
println(" i =" + i + " j = " + j)
}
i=1 j=1
i=1 j=2
i=1 j=3
i=1 j=4
i=2 j=1
i=2 j=2
i=2 j=3
i=2 j=4
i=3 j=1
i=3 j=2
i=3 j=3
i=3 j=4
等价于
for (i <- 1 to 3) {
for (j <- 1 to 3) {
println("i =" + i + " j=" + j)
} }
引入变量
for(i <- 1 to 3; j = 4 - i) {
println("i=" + i + " j=" + j)
}
- for 推导式一行中有多个表达式时,所以要加 ; 来隔断逻辑
- for 推导式有一个不成文的约定:当 for 推导式仅包含单一表达式时使用圆括号,
- 当包含多个表达式时,一般每行一个表达式,并用花括号代替圆括号,如下
for {
i <- 1 to 3
j = 4 - i
} {
println("i=" + i + " j=" + j)
}
等价于
for (i <- 1 to 3) {
var j = 4 - i
println("i=" + i + " j=" + j)
}
循环返回值 yield
注意:开发中很少使用。
说明:将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字
val res = for(i <- 1 to 10) yield i*2
println(res)
Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
倒序打印
for(i <- 1 to 10 reverse){
println(i)
}
while 和 do while 循环
与 for 语句不同,while 语句没有返回值,即整个 while 语句的结果是 Unit 类型()
do…while 循环是先执行,再判断
object TestWhile {
def main(args: Array[String]): Unit = {
var i=0
do{
println("狗头")
i+=1
}while(i<10)
}
}
循环中断
Scala 内置控制结构特地去掉了 break 和 continue
Scala中使用breakable控制结构来实现 break 和 continue 功能。
- 需求1:采用异常的方式退出循环
object TestBreak {
try {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) throw RuntimeException
}
} catch {
case e =>
}
println("正常结束循环")
}
-
需求2:采用Scala自带的函数,退出循环
object TestBreak { def main(args: Array[String]): Unit = { Breaks.breakable { for (elem <- 1 to 10) { println(elem) if (elem == 5) Breaks.break() } } println("正常结束循环") } }
-
需求3: 对break 进行省略
import scala.util.control.Breaks._ object TestBreak { def main(args: Array[String]): Unit = { breakable { for (elem <- 1 to 10) { println(elem) if (elem == 5) break() } } println("正常结束循环") } }
函数式编程
基本语法
函数和方法的区别
(1)为完成某一功能的程序语句的集合,称为函数。
(2)类中的函数称之方法。
函数没有重载和重写的概念;方法可以进行重载和重写
package TestFunction
object TestFunction {
def main(): Unit = {
}
// 方法可以进行重载和重写,程序可以执行
def main(args: Array[String]): Unit = {
// scala 语言可以在任何语法中声明任何语法
import java.util.Date
new Date()
// 函数没有重载和重写的概念,程序报错
// def test(): Unit = {
// println("无参,无返回值")
// }
def test(name: String): Unit = {
println("无参,无返回值")
}
// Scala 中函数可以嵌套定义
def test2():Unit={
def test3(name:String):Unit={
println("函数可以嵌套定义")
}
}
}
}
函数参数
- 可变参数,如果参数列表中存在多个参数,那么可变参数一般放在最后
- 参数默认值,一般将默认值的参数放置在参数列表的后面
- 带名参数
object TestFunction {
def main(args: Array[String]): Unit = {
def test(s: String*): Unit = {
println(s)
}
test("Hello","Scala")
test()
// (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
def test2( name : String, s: String* ): Unit = {
println(name + "," + s)
}
test2("jinlian", "dalang")
// (3)参数默认值
def test3( name : String, age : Int = 30 ): Unit = {
println(s"$name, $age")
}
// 如果参数传递了值,那么会覆盖默认值
test3("jinlian", 20)
// 如果参数有默认值,在调用的时候,可以省略这个参数
test3("dalang")
// 一般情况下,将有默认值的参数放置在参数列表的后面
def test4( sex : String = "男", name : String ): Unit = {
println(s"$name, $sex")
}
// Scala 函数中参数传递是,从左到右
//test4("wusong")
//(4)带名参数
test4(name="ximenqing")
}
}
函数至简 能省就省
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略 ( 和返回值类型一起省略 )
- 如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
object TestFunction2 {
def main(args: Array[String]): Unit = {
// 标准写法
def f(s: String): String = {
return s + "jj"
}
println(f("h"))
//(1) return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
def f2(s: String): String = {
s + "jj"
}
println(f2("h"))
//(2)如果函数体只有一行代码,可以省略花括号
def f3(s: String): String = s + "jj"
//(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
def f4( s : String ) = s + " jinlian"
println(f4("Hello3"))
//(4)如果有 return,则不能省略返回值类型,必须指定
def f5():String={
return "sda"
}
//(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
def f6(): Unit ={
return "dada"
}
println(f6()) // unit
//(6)Scala 如果期望是无返回值类型,可以省略等号
// 将无返回值的函数称之为过程
def f7(){
"dada"
}
println(f7())
// (7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def f8()="wucan"
println(f8)
// (8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f9 = "wuliebiao"
println(f9)
// (9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def f10= (x:String)=>{println("wusong")}
def f11(f:String=>Unit) = {
f("")
}
f11(f10)
println(f11((x:String)=>{println("wusong")}))
}
}
高阶函数
- 函数作为值传递
- 函数作为参数进行传递
- 函数可以作为函数返回值返回
object TestFunction3 {
def main(args: Array[String]): Unit = {
//(1)调用 foo 函数,把返回值给变量 f
//val f = foo()
val f = foo
println(f)
//(2)在被调用函数 foo 后面加上 _,相当于把函数 foo 当成一个整体, 传递给变量 f1
val f1 = foo _
f1()
//(3)如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给 变量
var f2:()=>Int=foo
}
def foo():Int={
println("floo..")
1
}
}
-
函数可以作为参数进行传递
object TestFunction5 { def main(args: Array[String]): Unit = { // (1)定义一个函数,函数参数还是一个函数签名;f 表示函数名称;(Int,Int) 表示输入两个 Int 参数;Int 表示函数返回值 def f1(f:(Int,Int)=>Int):Int={ f(2,4) } // (2)定义一个函数,参数和返回值类型和 f1 的输入参数一致 def add(a:Int,b:Int):Int=a+b // (3)将 add 函数作为参数传递给 f1 函数,如果能够推断出来不是调用,_ 可以省略 println(f1(add)) println(f1(add _)) //可以传递匿名函数 } }
-
函数可以作为函数返回值返回
def ff()={ def f2()={ } f2 _ } val fo=ff() // 因为 f1 函数的返回值依然为函数,所以可以变量 f 可以作为函数继续调用 fo() // 上面的代码可以简化为 ff()()
匿名函数
没有名字的函数就是匿名函数。
(x:Int)=>{函数体}
x:形参;Int:表示输入参数类型;函数体:表示具体代码逻辑
(1)参数的类型可以省略,会根据形参进行自动的推导
(2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参
数超过 1 的永远不能省略圆括号。
(3)匿名函数如果只有一行,则大括号也可以省略
(4)如果参数只出现一次,则参数省略且后面参数可以用_代替
object TestFunction4 {
def main(args: Array[String]): Unit = {
// (1)定义一个函数:参数包含数据和逻辑函数
def operation(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
// (4)采用匿名函数
val arr=operation(Array(1,2,3,4),(ele:Int)=>{
ele+1
})
println(arr.mkString(","))
// (4.1)参数的类型可以省略,会根据形参进行自动的推导;
val arr2=operation(Array(1,2,3,4),(ele)=>{
ele+1
})
println(arr2.mkString(","))
// (4.2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
val arr3=operation(Array(1,2,3,4),ele=>{
ele+1
})
println(arr3.mkString(","))
// (4.3) 匿名函数如果只有一行,则大括号也可以省略
val arr4=operation(Array(1,2,3,4),ele=>ele+1)
println(arr4.mkString(","))
// (4.3) 匿名函数如果只有一行,则大括号也可以省略
val arr5=operation(Array(1,2,3,4),_+1)
println(arr5.mkString(","))
}
}
高阶函数案例
需求:模拟 Map 映射、Filter 过滤、Reduce 聚合
package study04
import scala.collection.mutable.ArrayBuffer
object TestFunction6 {
def main(args: Array[String]): Unit = {
// (1)map 映射
def map(array: Array[Int], op: Int => Int) = {
for (elem <- array) yield op(elem)
}
val arr = map(Array(1, 2, 3, 4), (x: Int) => {
x * x
})
println(arr.mkString(","))
}
// (2)filter 过滤。有参数,且参数再后面只使用一次,则参数省略且 后面参数用_表示
def filter(arr: Array[Int], op: Int => Boolean) = {
var arr1: ArrayBuffer[Int] = ArrayBuffer[Int]()
for (elem <- arr if op(elem)) {
arr1.append(elem)
}
arr1.toArray
}
var arr1 = filter(Array(1, 2, 3, 4), _ % 2 == 1)
println(arr1.mkString(","))
// (3)reduce 聚合。有多个参数,且每个参数再后面只使用一次,则参数省略且后面参数用_表示,第 n 个_代表第 n 个参数
def reduce(array: Array[Int], op: (Int, Int) => Int) = {
var init: Int = array(0)
for (elem <- 1 until array.length){
init=op(init,elem)
}
init
}
//val arr2 = reduce(Array(1, 2, 3, 4), (x, y) => x * y)
val arr2 = reduce(Array(1, 2, 3, 4), _ * _)
println(arr2)
} 6
函数柯里化&闭包
说明
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
object TestFunction {
def main(args: Array[String]): Unit = {
def f1()={
var a:Int = 10
def f2(b:Int)={
a + b
}
f2 _
}
// 在调用时,f1 函数执行完毕后,局部变量 a 应该随着栈空间释放掉
val f = f1()
// 但是在此处,变量 a 其实并没有释放,而是包含在了 f2 函数的内部,形
成了闭合的效果
println(f(3))
println(f1()(3))
// 函数柯里化,其实就是将复杂的参数逻辑变得简单化,函数柯里化一定存
在闭包
def f3()(b:Int)={
a + b
}
println(f3()(3))
} }
递归
一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
object TestFunction {
def main(args: Array[String]): Unit = {
// 阶乘
// 递归算法
// 1) 方法调用自身
// 2) 方法必须要有跳出的逻辑
// 3) 方法调用自身时,传递的参数应该有规律
// 4) scala 中的递归必须声明函数返回值类型
println(test(5))
}
def test(i : Int) : Int = {
if (i == 1) {
1
} else {
i * test(I - 1)
}
} }
控制抽象
注意:Java 只有值调用;Scala 既有值调用,又有名调用
-
值调用:把计算后的值传递过去
object TestControl { def main(args: Array[String]): Unit = { def f = ()=>{ println("f...") 10 } foo(f()) } def foo(a: Int):Unit = { println(a) println(a) } }
-
名调用:把代码传递过去
object TestControl { def main(args: Array[String]): Unit = { def f = () => { println("f...") 10 } foo(f()) } def foo(a: => Int): Unit = { println(a) println(a) } }
-
实操
自定义while
object TestControl { def main(args: Array[String]): Unit = { var i:Int=1 myWhile(i<=10){ println(i) i+=1 } } def myWhile(condition: =>Boolean)(op: =>Unit):Unit={ if(condition){ op myWhile(condition)(op) } } }
惰性加载
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数
注意:lazy 不能修饰 var 类型的变量
object TestFunction7 {
def sum(n1:Int,n2:Int):Int={
println("sum 执行----")
return n1+n2
}
def main(args: Array[String]): Unit = {
lazy val res=sum(10,2)
println("-----")
println("res= "+res)
}
}
-----
sum 执行----
res= 12
scala 面向对象
包的命名
com.公司名.项目名.业务模块名
包说明
第二种风格 嵌套式风格表示层级关系
package com{
package atguigu{
package scala{
}
}
}
第二种风格有以下特点:
(1)一个源文件中可以声明多个 package
(2)子包中的类可以直接访问父包中的内容,而无需导包 父包需要
package com{
import com.atguigu.scala.Inner
// 在外层包中定义单例对象
object Outer{
var out: String = "out"
def main(args: Array[String]): Unit = {
println(Inner.in)
}
}
package atguigu{
package scala{
// 内层包中定义单例对象
object Inner{
var in: String = "in"
def main(args: Array[String]): Unit = {
println(Outer.out)
Outer.out = "outer"
println(Outer.out)
}
}
}
}
}
包对象
在 Scala 中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有 class 和 object 的共享变量,可以被直接访问
-
若使用 Java 的包管理风格,则包对象一般定义在其对应包下的 package.scala文件中,包对象名与包名保持一致。
-
如采用嵌套方式管理包,则包对象可与包定义在同一文件中,但是要保证包对象与包声明在同一作用域中
package com{ object Outer{ val out:String="out" def main(args:ArrayList[String]):Unit={ println(name) } } } package object com{ val name:String = "com" }
导包说明
- 和 Java 一样,可以在顶部使用 import 导入,在这个文件中的所有类都可以使用。
- 局部导入:什么时候使用,什么时候导入。在其作用范围内都可以使用
- 通配符导入:import java.util._
- 给类起名:import java.util.{ArrayList=>JL}
- 导入相同包的多个类:import java.util.{HashSet, ArrayList}
- 屏蔽类:import java.util.{ArrayList =>,}
- 导入包的绝对路径:new root.java.util.HashMap
注意
Scala 中的三个默认导入分别是
import java.lang._
import scala._
import scala.Predef._
类和对象
注意:Scala 中没有 public,一个.scala 中可以写多个类。
- Scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是public)
- 一个 Scala 源文件可以包含多个类
[修饰符] class 类名 {
类体
}
属性
[修饰符] var|val 属性名称 [:类型] = 属性值
注:Bean 属性(@BeanPropetry),可以自动生成规范的 setXxx/getXxx 方法
class Person {
var name: String = "bob"
var age: Int = _ // _ 表示给属性一个默认值
// Bean 属性
@BeanProperty var sex:String= "男"
// val 修饰的属性不能赋默认值,必须显式指定
}
object Person{
def main(args: Array[String]): Unit = {
var person=new Person()
println(person.name)
person.setSex("女")
println(person.getSex)
}
}
封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。Java 封装操作如下,
(1)将属性进行私有化
(2)提供一个公共的 set 方法,用于对属性赋值
(3)提供一个公共的 get 方法,用于获取属性的值
Scala 中的 public 属性,底层实际为 private,并通过 get 方法(obj.field())和 set 方法(obj.field_=(value))对其进行操作。所以 Scala 并不推荐将属性设为 private,再为其设置public 的 get 和 set 方法的做法。但由于很多 Java 框架都利用反射调用 getXXX 和 setXXX 方法,有时候为了和这些框架兼容,也会为 Scala 的属性设置 getXXX 和 setXXX 方法**(通过@BeanProperty 注解实现)**
访问权限
(1)Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字。
(2)private 为私有权限,只在类的内部和伴生对象中可用。
(3)protected 为受保护权限,Scala 中受保护权限比 Java 中更严格,同类、子类可以访问,同包无法访问。
(4)private[包名]增加包访问权限,包名下的其他类也可以使用
方法
def 方法名 (参数列表) [:返回值类型] = {
方法体
}
创建对象
val | var 对象名 [:类型] = new 类型()
(1)val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
(2)var 修饰对象,可以修改对象的引用和修改对象的属性值
(3)自动推导变量类型不能多态,所以多态需要显示声明
class Person {
var name: String = "bob"
var age: Int = _ // _ 表示给属性一个默认值
// Bean 属性
@BeanProperty var sex:String= "男"
// val 修饰的属性不能赋默认值,必须显式指定
}
object Person{
def main(args: Array[String]): Unit = {
//val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值
val person=new Person()
person.name="cang"
// person=new Person()
println(person.name)
}
}
构造器
Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala 类的构造器包括:主构造器和辅助构造器
class 类名**(形参列表)** { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个…
}
}
// 1 如果主构造器无参数 小括号可省略
class Person { // this() 主构造器
var name:String=_
var age:Int=_
def this(age:Int){
this()
this.age=age
println("辅助构造器")
}
def this(age:Int,name:String){
this(age)
this.name=name
}
println("主构造器")
}
object Person{
def main(args: Array[String]): Unit = {
val person2=new Person(18);
}
}
说明:
(1)辅助构造器,函数的名称 this,可以有多个,编译器通过参数的个数及类型来区分。
(2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
(3)构造器调用其他另外的构造器,要求被调用构造器必须提前声明
构造器参数
Scala 类的主构造器函数的形参包括三种类型:未用任何修饰、var 修饰、val 修饰
(1)未用任何修饰符修饰,这个参数就是一个局部变量
(2)var 修饰参数,作为类的成员属性使用,可以修改
(3)val 修饰参数,作为类只读属性使用,不能修改
class Person(name:String,var age:Int,val sex:String){
}
object Test{
def main(args: Array[String]): Unit = {
var person=new Person("bobo",19,"男")
//(1) 未用任何修饰符修饰,这个参数就是一个局部变量
// printf(person.name)
//(2) var修饰参数 作为类的成员属性使用,可以修改
person.age=20
println(person.age)
// (3) val 修饰参数,作为类的只读属性使用,不能修改
// person.sex="女"
println(person.sex)
}
}
继承和多态
class 子类名 extends 父类名 { 类体 }
(1)子类继承父类的属性和方法
(2)scala 是单继承
(3)继承的调用顺序:父类构造器->子类构造器
class Person(nameParam: String) {
var name = nameParam
var age: Int = _
def this(nameParam: String, ageParam: Int) {
this(nameParam)
this.age = ageParam
println("父类辅助构造器")
}
println("父类主构造器")
}
class Emp(nameParam: String, ageParam: Int) extends Person(nameParam, ageParam) {
var empNo: Int = _
def this(nameParam: String, ageParam: Int, empNoParam: Int) {
this(nameParam, ageParam)
this.empNo=empNoParam
println("子类的辅助构造器")
}
println("子类主构造器")
}
object Test{
def main(args: Array[String]): Unit = {
new Emp("z3",11,1001)
}
}
动态绑定 对比 Java 与 Scala 的重写
运行时 绑定方法
scala override 关键字
class Person {
val name: String = "person"
def hello(): Unit = {
println("hello person")
}
}
class Teacher extends Person {
override val name: String = "teacher"
override def hello(): Unit = {
println("hello teacher")
}
}
object Test {
def main(args: Array[String]): Unit = {
val teacher: Teacher = new Teacher()
println(teacher.name)
teacher.hello()
val teacher1:Person = new Teacher
println(teacher1.name)
teacher1.hello()
}
}
class Person {
public String name = "person";
public void hello() {
System.out.println("hello person");
}
}
class Teacher extends Person {
public String name = "teacher";
@Override
public void hello() {
System.out.println("hello teacher");
}
}
public class TestDynamic {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Person teacher1 = new Teacher();
System.out.println(teacher.name);
teacher.hello();
System.out.println(teacher1.name);
teacher1.hello();
}
}
Scala 中属性和方法都是动态绑定,而 Java 中只有方法为动态绑定,属性静态绑定(编译时绑定好了)。
抽象类
抽象属性和抽象方法
(1)定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
(2)定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
(3)定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
abstract class Person {
val name: String
def hello(): Unit
}
class Teacher extends Person {
val name: String = "teacher"
def hello(): Unit = {
println("hello teacher")
}
}
-
如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
-
重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。
-
子类中调用父类的方法使用 super 关键字
-
子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;
-
子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var。
因为 var 修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写
匿名子类
可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类
abstract class Person {
val name: String = "person"
def hello():Unit
}
object Test{
def main(args: Array[String]): Unit = {
val person=new Person {
override val name: String = "teacher"
override def hello(): Unit = println("hello teacher")
}
}
}
单例对象(伴生对象)
object Person{
val country:String=“China”
}
若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明
// (1) 伴生对象采用object 关键字声明
object Person{
var country:String="china"
}
//(2)伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
class Person {
var name: String = "bobo"
}
object Test{
def main(args: Array[String]): Unit = {
//(3)伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
println(Person.country)
}
}
(1)单例对象采用 object 关键字声明
(2)单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
(3)单例对象中的属性和方法都可以通过**伴生对象名(类名)**直接调用访问
apply 方法
(1)通过伴生对象的 apply 方法,实现不使用 new 方法创建对象。
(2)如果想让主构造器变成私有的,可以在()之前加上 private。
(3)apply 方法可以重载。
(4)Scala 中 **obj(arg)**的语句实际是在调用该对象的 apply 方法,即 obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。
(5)当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的 apply 方法。
// (1) 伴生对象采用object 关键字声明
object Person{
def apply():Person={
println("apply 空参被调用")
new Person("xx")
}
//注意:也可以创建其它类型对象,并不一定是伴生类对象
def apply(name: String): Person = {
println("apply 有参被调用")
new Person(name)
}
}
//(2)如果想让主构造器变成私有的,可以在()之前加上 private
class Person private(cName:String){
var name: String = cName
}
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)
}
}
特质 Trait
Scala 语言中,采用特质 trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。
Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类
trait 特质名 {
trait 主体
}
通过查看字节码,可以看到特质=抽象类+接口
trait PersonTrait {
// 声明属性
var name:String=_
// 声明方法
def eat():Unit={
}
// 抽象属性
var age:Int
// 抽象方法
def say():Unit
}
特质基本语法
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了 extends 关键字,如果有多个特质或存在父类,那么需要采用 with关键字连接
基本语法:
- 没有父类:class 类名 extends 特质 1 with 特质 2 with 特质 3 …
- 有父类:class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3…
说明
- 类和特质的关系:使用继承的关系。
- 当一个类去继承特质时,第一个连接词是 extends,后面是 with。
- 如果一个类在同时继承特质和父类时,应当把父类写在 extends 后
案例实操
- 特质可以同时拥有抽象方法和具体方法
- 一个类可以混入(mixin)多个特质
- 所有的 Java 接口都可以当做 Scala 特质使用
- 动态混入:可灵活的扩展类的功能
- 动态混入:创建对象时混入 trait,而无需使类混入该trait
- 如果混入的 trait 中有未实现的方法,则需要实现
trait PersonTrait {
//(1)特质可以同时拥有抽象方法和具体方法
// 声明属性
var name: String = _
// 抽象属性
var age: Int
// 声明方法
def eat(): Unit = {
println("eat")
}
// 抽象方法
def say(): Unit
}
trait SexTrait{
var sex:String
}
//(2)一个类可以实现/继承多个特质
//(3)所有的 Java 接口都可以当做 Scala 特质使用
class Teacher extends PersonTrait with java.io.Serializable {
override def say(): Unit = {
println("say")
}
override var age: Int = _
}
object TestTrait{
def main(args: Array[String]): Unit = {
val teacher = new Teacher
teacher.say()
teacher.eat()
//(4)动态混入:可灵活的扩展类的功能
val t2 = new Teacher with SexTrait {
override var sex: String = "男"
}
//调用混入 trait 的属性
println(t2.sex)
}
}
特质叠加
由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:
- 第一种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法
- 第二种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略
- 所谓的特质叠加,就是将混入的多个 trait 中的冲突方法叠加起来,案例如下
package study05
trait Ball{
def describe():String={
"ball"
}
}
trait Color extends Ball{
override def describe(): String = {
"blue-"+super.describe()
}
}
trait Catagory extends Ball{
override def describe(): String = {
"foot-"+super.describe()
}
}
class MyBall extends Catagory with Color {
override def describe(): String = {
"my ball is a "+super.describe()
}
}
object TestTrait02{
def main(args: Array[String]): Unit = {
println(new MyBall().describe())
}
}
结果
my ball is a blue-foot-ball
特质叠加执行顺序
**思考:**上述案例中的 super.describe()调用的是父 trait 中的方法吗?
当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行排序**,而此案例中的 super.describe()调用的实际上是排好序后的下一个特质中的 describe()方法**。,排序规则如下
- 案例中的 super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass 中的 super 指代 Color,Color 中的 super 指代 Category,Category 中的 super 指代 Ball。
- 如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如 super[Category].describe()
特质自身
自身类型可实现依赖注入的功能
// 用户类
class User(val name: String, val password: String)
trait UserDao {
// 没看懂
// 这就依赖注入啦
// 自身类型命名为_ 并且限定为UserDao 实例化 必须携带user
_: User =>
// 向数据库插入数据
def insert(): Unit = {
println(s"insert into db: ${this.name}")
}
}
// 定义注册用户类
class RegisterUser(name: String, password: String) extends User(name, password) with UserDao
object TestUser{
def main(args: Array[String]): Unit = {
val user = new RegisterUser("alice", "123456")
user.insert()
}
}
特质和抽象类的区别
- 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类(单继承,多实现)。
- 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)
扩展
类型检查和转换
- obj.isInstanceOf[T]:判断 obj 是不是 T 类型。
- obj.asInstanceOf[T]:将 obj 强转成 T 类型
- classOf 获取对象的类名
枚举类和应用类
枚举类:需要继承 Enumeration
应用类:需要继承 App
object他继承了App类,没有写main方法,但是可以生产运行,这是由于App类是Scala自己提供的一个类,它的作用是
当object继承它时,不需要写main方法,而是将整个的类看做一个main方法, 可以直接运行
object testApp extends App {
println("app start")
type MyString = String
val a: MyString = "abc"
println(a)
}
// 枚举类
object EnColor extends Enumeration{
val RED=Value(1,"red")
val YELLOW=Value(2,"yellow")
val BLUE=Value(3,"blue")
}
object TestColor{
def main(args: Array[String]): Unit = {
println(EnColor.RED)
}
}
type 定义新类型
使用 type 关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名
object TestColor{
def main(args: Array[String]): Unit = {
type S=String
var v:S="abc"
def test():S="xyz"
}
}
集合 Seq Set Map
Scala 的集合有三大类:序列 Seq、集 Set、映射 Map,所有的集合都扩展自 Iterable特质
对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两个包
- 不可变集合:scala.collection.immutable
- 可变集合: scala.collection.mutable
Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而不会对原对象进行修改。类似于 java 中的 String 对象
可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似于 java 中 StringBuilder 对象
建议:在操作集合的时候,不可变用符号,可变用方法
不可变集合
for 循环有一个 1 to 3,就是 IndexedSeq 下的 Range
String 也是属于 IndexedSeq
IndexedSeq 是通过索引来查找和定位,因此速度快,比如 String 就是一个索引集合,通过索引即可定位
LinearSeq 是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找
可变集合
数组
不可变数组
-
第一种方式定义数组
定义:val arr1 = new Array [Int] (10)
new 是关键字
[Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定 Any
(10),表示数组的大小,确定后就不可以变化
.:+ 运算符重载? :在前面 就是元素加在前面 :在后面就是加在后面
点号可以省略 空格隔开
package study06 class testArray { } object testArray{ def main(args: Array[String]): Unit = { // 1. 创建数组 val arr:Array[Int]=new Array[Int](5) // 另一种创建方式 val arr2=Array(12,23,43,53,54) // 2.访问元素 println(arr(0)) arr(0)=4231 println(arr(0)) println("=====================") // 3. 数组的遍历 // 3.1 普通for循环 for(i<-0 until arr.length){ println(arr(i)) } println("3.1 =====================") // 3.2 直接遍历所有元素 增强for循环 for(elem<-arr2) println(elem) println("3.2 =====================") // 3.3 迭代器 val iter=arr2.iterator while (iter.hasNext) println(iter.next()) println("3.3-------------------") // 3.4 调用foreach 方法 arr2.foreach((elem:Int)=>println(elem)) arr2.foreach(println) println(arr2.mkString(",")) println("3.4 =====================") // 4 添加元素 .:+ val newArr=arr2.:+(72) println(arr2.mkString("__")) println(newArr.mkString(",")) // 元素在前 对象在后 冒号 在对象前面 反之冒号在对象后面 val newArr4=19+:29+:newArr:+26:+777 println(newArr4.mkString(", ,")) } }
就记住一句话 :永远挨着已有集合的那边就可以了
-
第二种
val arr1 = Array(1, 2)
在定义数组时,直接赋初始值——使用 apply 方法创建数组对象
可变数组
定义变长数组
val arr01 = ArrayBuffer [Any] (3, 2, 5)
-
ArrayBuffer 是有序的集合
-
增加元素使用的是 append 方法(),支持可变参数
[Any]存放任意数据类型
(3, 2, 5)初始化好的三个元素
ArrayBuffer 需要引入 scala.collection.mutable.ArrayBuffer
object TestArrayBuffer {
def main(args: Array[String]): Unit = {
// 创建并初始赋值可变数组
var arr01=ArrayBuffer[Any](1,2,3)
// 遍历数组
for(i<-arr01){
println(i)
}
println("arr01.hash= "+arr01.hashCode())
// 增加元素
// 3.1 追加数据
arr01 += 4
arr01.+=(4)
// 3.2 向数组最后追加数据
arr01.append(5,6)
// 3.3 向指定的位置插入数据 0号位置
arr01.insert(0,7,8)
println("arr01.hash= "+arr01.hashCode())
// 4 修改元素
arr01(1)=9
println("---------------------")
for(i<-arr01){
println(i)
}
println(arr01.length)
}
}
不可变数组与可变数组的转换
arr1.toBuffer //不可变数组转可变数组
arr2.toArray //可变数组转不可变数组
- arr2.toArray 返回结果才是一个不可变数组,arr2 本身没有变化
- arr1.toBuffer 返回结果才是一个可变数组,arr1 本身没有变化
多维数组
多维数组定义
val arr = Array.ofDim [Double] (3,4)
说明:二维数组中有三个一维数组,每个一维数组中有四个元素
object DimArray {
def main(args: Array[String]): Unit = {
// 创建一个二维数组,有三个元素,每个元素是,含有4个元素一维数组()
val arr=Array.ofDim[Int](3,4)
// 2 遍历二维数组
for(i<-arr){ // i就是一维数组
for(j<-i){
print(j+" ")
}
println()
}
}
}
列表 List
不可变 List
List 默认为不可变集合
集合间合并:将一个整体拆成一个一个的个体,称为扁平化
List子类 空集合对象 Nil
:: List 子类 extends List[B]
sealed 密封类
package study06
class testList {
}
object testList{
// 1 List 默认为不可变集合
// 2 创建一个List (数据有顺序,可重复)
val list1:List[Int]=List(1,2,3,4,3)
val list2=List(1,2,3,4,3)
list1.foreach(println)
// 3. 添加元素
val list3 = 10 +: list1
val list4 = list1 :+ 23
println(list4)
println(list3)
println("==================")
//添加到第一个元素位置
val list5 = list2.::(51)
println(list4)
// 创建新列表
val list6 = Nil.::(13)
println(list5)
val list7 = 17 :: 28 :: 59 :: 16 :: Nil
println(list7)
// 4. 合并列表
val list8 = list6 :: list7 // 不能合并
println(list8)
val list9 = list6 ::: list7
println(list9)
val list10 = list6 ++ list7
println(list10)
}
可变 ListBuffer
package chapter07
import scala.collection.mutable.ListBuffer
object Test05_ListBuffer {
def main(args: Array[String]): Unit = {
// 1. 创建可变列表
val list1: ListBuffer[Int] = new ListBuffer[Int]()
val list2 = ListBuffer(12, 53, 75)
println(list1)
println(list2)
println("==============")
// 2. 添加元素
list1.append(15, 62)
list1.insert(1, 19, 22)
list2.prepend(20)
println(list1)
println(list2)
println("==============")
31 +=: 96 +=: list1 += 25 += 11
println(list1)
println("list1 ==============")
// 3. 合并list ++不改变list1 list2
val list3 = list1 ++ list2
println(list3)
println("list3 ==============")
// ++= 覆盖
list1 ++=: list2
println(list1)
println(list2)
println("==============")
// 4. 修改元素
list2(3) = 30
list2.update(0, 89)
println(list2)
// 5. 删除元素
list2.remove(2)
list2 -= 25
println(list2)
}
}
Set 集合
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用scala.collection.mutable.Set 包
Set 默认是不可变集合,数据无序,数据不可重复
不可变 Set
package chapter07
object Test06_ImmutableSet {
def main(args: Array[String]): Unit = {
// 1. 创建set
val set1 = Set(13, 23, 53, 12, 13, 23, 78)
println(set1)
println("==================")
// 2. 添加元素
val set2 = set1 + 129
println(set1)
println(set2)
println("==================")
// 3. 合并set
val set3 = Set(19, 13, 23, 53, 67, 99)
val set4 = set2 ++ set3
println(set2)
println(set3)
println(set4)
// 4. 删除元素
val set5 = set3 - 13
println(set3)
println(set5)
}
}
可变 Set
object Test07_MutableSet {
def main(args: Array[String]): Unit = {
// 1. 创建set
val set1: mutable.Set[Int] = mutable.Set(13, 23, 53, 12, 13, 23, 78)
val s = mutable.Set(13, 23, 53, 12, 13, 23, 78)
println(set1)
println("==================")
// 2. 添加元素
val set2 = set1 + 11
println(set1)
println(set2)
set1 += 11
println(set1)
val flag1 = set1.add(10) // 返回 boolean true
println(flag1)
println(set1)
val flag2 = set1.add(10)
println(flag2)
println(set1)
println("==================")
// 3. 删除元素
set1 -= 11
println(set1)
val flag3 = set1.remove(10) // 返回 boolean true
println(flag3)
println(set1)
val flag4 = set1.remove(10)
println(flag4)
println(set1)
println("==================")
// 4. 合并两个Set
val set3 =set1.remove(10)
}
}
Map 集合
不可变
object Test08_ImmutableMap {
def main(args: Array[String]): Unit = {
// 1. 创建map
val map1: Map[String, Int] = Map("a" -> 13, "b" -> 25, "hello" -> 3)
val m1= Map("a" -> 13, "b" -> 25, "hello" -> 3)
println(map1)
println(map1.getClass)
println("==========================")
// 2. 遍历元素
map1.foreach(println)
println("foreach ==========================")
map1.foreach( (kv: (String, Int)) => println(kv) )
println("============================")
// 3. 取map中所有的key 或者 value
for (key <- map1.keys){
println(s"$key ---> ${map1.get(key)}")
}
// 4. 访问某一个key的value
println("a: " + map1.get("a").get)
println("c: " + map1.get("c"))
println("c: " + map1.getOrElse("c", 0))
println(map1("a"))
}
}
可变
package chapter07
import scala.collection.mutable
object Test09_MutableMap {
def main(args: Array[String]): Unit = {
// 1. 创建map
val map1: mutable.Map[String, Int] = mutable.Map("a" -> 13, "b" -> 25, "hello" -> 3)
val m = mutable.Map("a" -> 13, "b" -> 25, "hello" -> 3)
println(map1)
println(map1.getClass)
println("1==========================")
// 2. 添加元素
map1.put("c", 5)
map1.put("d", 9)
println(map1)
map1 += (("e", 7))
println(map1)
println("2====================")
// 3. 删除元素
println(map1("c"))
map1.remove("c")
println(map1.getOrElse("c", 0))
map1 -= "d"
println(map1)
println("3====================")
// 4. 修改元素
map1.update("c", 5)
map1.update("e", 10)
println(map1)
println("4====================")
// 5. 合并两个Map
val map2: Map[String, Int] = Map("aaa" -> 11, "b" -> 29, "hello" -> 5)
// map1 ++= map2
println(map1)
println(map2)
println("---------------------------")
val map3: Map[String, Int] = map2 ++ map1
val m3 = map2 ++ map1
println(map1)
println(map2)
println(map3)
}
}
元组
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组
注意:元组中最大只能有 22 个元素。
object Test10_Tuple {
def main(args: Array[String]): Unit = {
// 1. 创建元组
val tuple: (String, Int, Char, Boolean) = ("hello", 100, 'a', true)
println(tuple)
// 2. 访问数据
println(tuple._1)
println(tuple._2)
println(tuple._3)
println(tuple._4)
// 2.1 通过索引访问数据
println(tuple.productElement(1))
println("====================")
// 3. 遍历元组数据 迭代器
for (elem <- tuple.productIterator)
println(elem)
// 3.1 Map 中的键值对其实就是元组,只不过元组的元素个数为 2,称之为 对偶
val map = Map("a"->1, "b"->2, "c"->3)
val map1 = Map(("a",1), ("b",2), ("c",3))
println("====================")
// 4. 嵌套元组
val mulTuple = (12, 0.3, "hello", (23, "scala"), 29)
println(mulTuple._4._2)
}
}
函数
基本属性和常用操作
object testCommonOp {
private val list: List[Int] = List(1, 2, 3, 4, 5, 6)
// 1 获取集合长度
println(list.length)
// 2 获取集合大小,等同于 length
println(list.size)
// 3 循环遍历
list.foreach(println)
// 4 迭代器
for(elem<-list.iterator){
println(elem)
}
// 5 生成字符串
println(list.mkString(","))
// 6 是否包含
println(list.contains(6))
}
衍生集合
package study06
object testCollectionDer {
def main(args: Array[String]): Unit = {
val list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
val list2: List[Int] = List(4, 5, 6, 7, 8, 9, 10)
// 1 获取集合的头
println(list1.head)
// 2 获取集合的尾
println(list1.tail)
// 3 集合最后一个数据
println(list1.last)
// 4 集合初始数据(不包含最后一个)
println(list1.init)
// 5 反转
println(list1.reverse)
// 6 取前 (后) n 个元素
println(list1.take(3))
println(list1.takeRight(3))
// 7 去掉前后n个元素
println(list1.drop(3))
println(list1.dropRight(3))
// 8 并集
println(list1.union(list2))
// 9 交集
println(list1.intersect(list2))
// 10 差集
println(list1.diff(list2))
// 11 拉链 注:如果两个集合的元素个数不相等,那么会将同等数量的数据进行拉链,多余的数据省略不用
println("-----------------")
println(list1.zip(list2))
// 12 滑窗
list1.sliding(2,5).foreach(println)
}
}
集合计算简单函数
package chapter07
object Test13_SimpleFunction {
def main(args: Array[String]): Unit = {
val list = List(5,1,8,2,-3,4)
val list2 = List(("a", 5), ("b", 1), ("c", 8), ("d", 2), ("e", -3), ("f", 4))
// (1)求和
var sum = 0
for (elem <- list){
sum += elem
}
println(sum)
println(list.sum)
// (2)求乘积
println(list.product)
println("m========================")
// (3)最大值
println(list.max)
println(list2.maxBy( (tuple: (String, Int)) => tuple._2 ))
println(list2.maxBy( _._2 ))
// (4)最小值
println(list.min)
println(list2.minBy(_._2))
println("========================")
// (5)排序
// 5.1 sorted
val sortedList = list.sorted
println(sortedList)
// 从大到小逆序排序
println(list.sorted.reverse)
// 传入隐式参数
println(list.sorted(Ordering[Int].reverse))
// 按照 abcd 的顺序排序 元组的第一个元素
println(list2.sorted)
// 5.2 sortBy 按照数字大小排序
println(list2.sortBy(_._2))
println(list2.sortBy(_._2)(Ordering[Int].reverse))
// 绝对值大小输出 并不改变原本元素
println("abs========================")
println(list.sortBy(x => x.abs))
// 5.3 sortWith
println(list.sortWith( (a: Int, b: Int) => {a < b} ))
println(list.sortWith( _ < _ ))
// 按元素降序排序
println(list.sortWith( _ > _))
}
}
(1)sorted
对一个集合进行自然排序,通过传递隐式的 Ordering
(2)sortBy
对一个属性或多个属性进行排序,通过它的类型。
(3)sortWith
基于函数的排序,通过一个 comparator 函数,实现自定义排序的逻辑。
集合计算高级函数
-
Map
过滤,转化/映射,扁平化,分组,简化(归约),折叠
package chapter07 object Test14_HighLevelFunction_Map { def main(args: Array[String]): Unit = { val list = List(1,2,3,4,5,6,7,8,9) // 1. 过滤 // 选取偶数 val evenList = list.filter( (elem: Int) => {elem % 2 == 0} ) println(evenList) // 选取奇数 println(list.filter( _ % 2 == 1 )) println("=======================") // 2. 映射map // 把集合中每个数乘2 println(list.map(_ * 2)) println(list.map( x => x * x)) println("=======================") // 3. 扁平化 flaten val nestedList: List[List[Int]] = List(List(1,2,3),List(4,5),List(6,7,8,9)) val flatList = nestedList(0) ::: nestedList(1) ::: nestedList(2) println(flatList) val flatList2 = nestedList.flatten println(flatList2) println("ff=======================") // 4. 扁平映射 // 将一组字符串进行分词,并保存成单词的列表 val strings: List[String] = List("hello world", "hello scala", "hello java", "we study") val splitList: List[Array[String]] = strings.map( _.split(" ") ) // 分词 val flattenList = splitList.flatten // 打散扁平化 println(flattenList) //flatMap 相当于先进行 map 操作,在进行 flatten 操作 //集合中的每个元素的子元素映射到某个函数并返回新集合 val flatmapList = strings.flatMap(_.split(" ")) println(flatmapList) println("========================") // 5. 分组groupBy // 分成奇偶两组 val groupMap: Map[Int, List[Int]] = list.groupBy( _ % 2) val groupMap2: Map[String, List[Int]] = list.groupBy( data => if (data % 2 == 0) "偶数" else "奇数") println(groupMap) println(groupMap2) // 给定一组词汇,按照单词的首字母进行分组 val wordList = List("china", "america", "alice", "canada", "cary", "bob", "japan") println( wordList.groupBy( _.charAt(0) ) ) } }
-
Reduce:通过指定的逻辑将集合中的数据进行聚合,从而减少数据,最终获取结果。
-
Fold
object Test15_HighLevelFunction_Reduce { def main(args: Array[String]): Unit = { val list = List(1,2,3,4) // 1. reduce println(list.reduce( _ + _ )) println(list.reduceLeft(_ + _)) println(list.reduceRight(_ + _)) // (1+(2+(3+4))) println("===========================") val list2 = List(3,4,5,8,10) println(list2.reduce(_ - _)) // -24=3-4-5-8-10 println(list2.reduceLeft(_ - _)) println(list2.reduceRight(_ - _)) // 3 - (4 - (5 - (8 - 10))), 6 println("===========================") // 2. fold // fold 方法使用了函数柯里化,存在两个参数列表 // 第一个参数列表为 : 零值(初始值) // 第二个参数列表为: 简化规则 // fold 底层其实为 foldLeft println(list.fold(10)(_ + _)) // 10 + 1 + 2 + 3 + 4 println(list.foldLeft(10)(_ - _)) // 10 - 1 - 2 - 3 - 4 println(list2.foldRight(11)(_ - _)) // 3 - (4 - (5 - (8 - (10 - 11)))), -5 // 标准写法 println(list2.fold(1)((x,y)=>x-y)) } }
-
map 合并案例
object Test16_MergeMap { def main(args: Array[String]): Unit = { val map1 = Map("a" -> 1, "b" -> 3, "c" -> 6) val map2 = mutable.Map("a" -> 6, "b" -> 2, "c" -> 9, "d" -> 3) // println(map1 ++ map2) 输出map2 val map3 = map1.foldLeft(map2)( (mergedMap, kv) => { val key = kv._1 val value = kv._2 mergedMap(key) = mergedMap.getOrElse(key, 0) + value mergedMap } ) println(map3) } }
wordcount 案例
单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果
普通版本
object testWordCount {
def main(args: Array[String]): Unit = {
// 单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果
val stringList = List("Hello Scala Hbase kafka", "Hello Scala Hbase", "Hello Scala", "Hello")
// 1) 将每一个字符串转换成一个一个单词
val wordList: List[String] = stringList.flatMap(str=>str.split(" "))
// 2) 将相同的单词放置在一起
val wordToWordsMap: Map[String, List[String]] = wordList.groupBy(word=>word)
// 3) 对相同的单词进行计数
val wordCountMap: Map[String, Int] = wordToWordsMap.map(tuple => (tuple._1, tuple._2.size))
// 4) 对计数完成后的结果进行排序(降序)
val sortList:List[(String,Int)]=wordCountMap.toList.sortWith{
(left,right)=>{
left._2>right._2
}
}
// 5) 对排序后的结果取前 3 名
val resultList: List[(String, Int)] = sortList.take(3)
println(resultList)
}
}
复杂版本
-
方式一
package study06 object testWordCount02 { def main(args: Array[String]): Unit = { // 第一种方式(不通用) val tupleList = List(("Hello Scala Spark World ", 4), ("Hello Scala Spark", 3), ("Hello Scala", 2), ("Hello", 1)) val stringList: List[String] = tupleList.map(t=>(t._1 + " ") * t._2) //val words: List[String] = stringList.flatMap(s=>s.split(" ")) val words: List[String] = stringList.flatMap(_.split(" ")) // 在map中,如果传进来什么就返回什么,不要用_省略 //val groupMap: Map[String, List[String]] = words.groupBy(_) val groupMap:Map[String,List[String]]= words.groupBy(word=>word) val wordToCount:Map[String,Int]=groupMap.map(t=>(t._1,t._2.size)) val wordCountList:List[(String,Int)]=wordToCount.toList.sortWith{ (left,right)=>{ left._2>right._2 } }.take(3) //tupleList.map(t=>(t._1 + " ") * t._2).flatMap(_.split(" ")).groupBy(word=>word).map(t=>(t._1, t._2.size)) println(wordCountList) } }
-
方式二
object testWordCount03 { def main(args: Array[String]): Unit = { val tuples = List(("Hello Scala Spark World", 4), ("Hello Scala Spark", 3), ("Hello Scala", 2), ("Hello", 1)) // (Hello,4),(Scala,4),(Spark,4),(World,4) // (Hello,3),(Scala,3),(Spark,3) // (Hello,2),(Scala,2) // (Hello,1) val wordToCountList: List[(String, Int)] = tuples.flatMap { t => { val strings: Array[String] = t._1.split(" ") strings.map(word => (word, t._2)) } } // Hello, List((Hello,4), (Hello,3), (Hello,2), (Hello,1)) // Scala, List((Scala,4), (Scala,3), (Scala,2) // Spark, List((Spark,4), (Spark,3) // Word, List((Word,4)) val wordToTupleMap: Map[String, List[(String, Int)]] = wordToCountList.groupBy(t=>t._1) val stringToInts: Map[String, List[Int]] = wordToTupleMap.mapValues { datas => datas.map(t => t._2) } stringToInts /* val wordToCountMap: Map[String, List[Int]] = wordToTupleMap.map { t => { (t._1, t._2.map(t1 => t1._2)) } } val wordToTotalCountMap: Map[String, Int] = wordToCountMap.map(t=>(t._1, t._2.sum)) println(wordToTotalCountMap) */ } }
队列
Scala 也提供了队列(Queue)的数据结构,队列的特点就是先进先出。进队和出队的方法分别为 enqueue 和 dequeue
object Test19_Queue {
def main(args: Array[String]): Unit = {
// 创建一个可变队列
val queue: mutable.Queue[String] = new mutable.Queue[String]()
queue.enqueue("a", "b", "c")
println(queue)
println(queue.dequeue())
println(queue)
println(queue.dequeue())
println(queue)
queue.enqueue("d", "e")
println(queue)
println(queue.dequeue())
println(queue)
println("==========================")
// 不可变队列
val queue2: Queue[String] = Queue("a", "b", "c")
val queue3 = queue2.enqueue("d")
println(queue2)
println(queue3)
}
}
并行集合
Scala 为了充分使用多核 CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算
object Test20_Parallel {
def main(args: Array[String]): Unit = {
val result: immutable.IndexedSeq[Long] = (1 to 100).map(
x => Thread.currentThread.getId
)
println(result)
// 并行集合
val result2: ParSeq[Long] = (1 to 100).par.map(
x => Thread.currentThread.getId
)
println(result2)
}
}
Vector(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
ParVector(20, 20, 20, 31, 27, 27, 30, 30, 30, 41, 41, 41, 26, 26, 26, 42, 42, 42, 33, 33, 33, 35, 43, 42, 31, 22, 22, 22, 43, 25, 34, 35, 35, 35, 40, 40, 40, 27, 27, 27, 27, 27, 27, 34, 34, 34, 43, 43, 43, 43, 21, 21, 21, 32, 32, 32, 25, 25, 25, 31, 31, 31, 24, 24, 24, 38, 38, 38, 29, 29, 29, 39, 39, 41, 32, 23, 23, 23, 40, 30, 34, 36, 36, 36, 34, 34, 34, 28, 28, 28, 28, 28, 28, 37, 37, 37, 38, 32, 36, 39)
模式匹配
Scala 中的模式匹配类似于 Java 中的 switch 语法,但是 scala 从语法中补充了更多的功能,所以更加强大。
基本语法
模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有 case 都不匹配,那么会执行 case _分支,类似于 Java 中default 语句
object Test01_PatternMatchBase {
def main(args: Array[String]): Unit = {
// 1. 基本定义语法
val x: Int = 5
val y: String = x match {
case 1 => "one"
case 2 => "two"
case 3 => "three"
case _ => "other"
}
println(y)
// 2. 示例:用模式匹配实现简单二元运算
val a = 25
val b = 13
def matchDualOp(op: Char): Int = op match {
case '+' => a + b
case '-' => a - b
case '*' => a * b
case '/' => a / b
case '%' => a % b
case _ => -1
}
println(matchDualOp('+'))
println(matchDualOp('/'))
println(matchDualOp('\\'))
println("=========================")
// 3. 模式守卫
// 求一个整数的绝对值
def abs(num: Int): Int = {
num match {
case i if i >= 0 => i
case i if i < 0 => -i
}
}
println(abs(67))
println(abs(0))
println(abs(-24))
}
}
注意:
- 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句,若此时没有 case _ 分支,那么会抛出 MatchError。
- 每个 case 中,不需要使用 break 语句,自动中断 case。
- match case 语句可以匹配任何类型,而不只是字面量。
- => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括
模式守卫
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫
object Test01_PatternMatchBase {
def main(args: Array[String]): Unit = {
// 3. 模式守卫
// 求一个整数的绝对值
def abs(num: Int): Int = {
num match {
case i if i >= 0 => i
case i if i < 0 => -i
}
}
println(abs(67))
println(abs(0))
println(abs(-24))
}
}
模式匹配类型
匹配常量
Scala 中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {
// 1. 匹配常量
def describeConst(x: Any): String = x match {
case 1 => "Int one"
case "hello" => "String hello"
case true => "Boolean true"
case '+' => "Char +"
case _ => ""
}
println(describeConst("hello"))
println(describeConst('+'))
println(describeConst(0.3))
}
}
匹配类型
需要进行类型判断时,可以使用前文所学的 isInstanceOf[T]和 asInstanceOf[T],也可使用模式匹配实现同样的功能
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {
// 2. 匹配类型
def describeType(x: Any): String = x match {
case i: Int => "Int " + i
case s: String => "String " + s
case list: List[_] => "List " + list
case array: Array[Int] => "Array[Int] " + array.mkString(",")
case a => "Something else: " + a
}
println(describeType(35))
println(describeType("hello"))
//泛型擦除
println(describeType(List("hi", "hello")))
println(describeType(List(2, 23)))
//数组例外,可保留泛型
println(describeType(Array("hi", "hello")))
println(describeType(Array(2, 23)))
}
}
匹配数组
scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为 0 的数组。
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {
// 3. 匹配数组
for (arr <- List(
Array(0),
Array(1, 0),
Array(0, 1, 0),
Array(1, 1, 0),
Array(2, 3, 7, 15),
Array("hello", 1, 30),
)) {
val result = arr match {
case Array(0) => "0"
case Array(1, 0) => "Array(1, 0)"
case Array(x, y) => "Array: " + x + ", " + y // 匹配两元素数组
case Array(0, _*) => "以0开头的数组"
case Array(x, 1, z) => "中间为1的三元素数组"
case _ => "something else"
}
println(result)
}
}
匹配列表
list 是一个存放 List 集合的数组
请思考,如果要匹配 List(88) 这样的只含有一个元素的列表,并原值返回.应该怎么写
-
方式一
object Test02_MatchTypes { def main(args: Array[String]): Unit = { for (list <- List( List(0), List(1, 0), List(0, 0, 0), List(1, 1, 0), List(88), List("hello") )) { val result = list match { case List(0) => "0" case List(x, y) => "List(x, y): " + x + ", " + y case List(0, _*) => "List(0, ...)" case List(a) => "List(a): " + a case _ => "something else" } println(result) } } }
-
方式二
object Test02_MatchTypes { def main(args: Array[String]): Unit = { val list1 = List(1, 2, 5, 7, 24) val list = List(24) list match { case first :: second :: rest => println(s"first: $first, second: $second, rest: $rest") case _ => println("something else") } } }
匹配元组
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {
for (tuple <- List(
(0, 1),
(0, 0),
(0, 1, 0),
(0, 1, 1),
(1, 23, 56),
("hello", true, 0.5)
)){
val result = tuple match {
case (a, b) => "" + a + ", " + b
case (0, _) => "(0, _)"
case (a, 1, _) => "(a, 1, _) " + a
case (x, y, z) => "(x, y, z) " + x + " " + y + " " + z
case _ => "something else"
}
println(result)
}
}
扩展案例
object TestGeneric {
def main(args: Array[String]): Unit = {
//特殊的模式匹配 1 打印元组第一个元素
for (elem <- Array(("a", 1), ("b", 2), ("c", 3))) {
println(elem._1)
}
for ((word,count) <- Array(("a", 1), ("b", 2), ("c", 3))) {
println(word)
}
for ((word,_) <- Array(("a", 1), ("b", 2), ("c", 3))) {
println(word)
}
for (("a",count) <- Array(("a", 1), ("b", 2), ("c", 3))) {
println(count)
}
println("--------------")
//特殊的模式匹配 2 给元组元素命名
var (id,name,age): (Int, String, Int) = (100, "zs", 20)
println((id,name,age))
println("--------------")
//特殊的模式匹配 3 遍历集合中的元组,给 count * 2
var list: List[(String, Int)] = List(("a", 1), ("b", 2), ("c", 3))
//println(list.map(t => (t._1, t._2 * 2)))
println(
list.map{
case (word,count)=>(word,count*2)
}
)
var list1 = List(("a", ("a", 1)), ("b", ("b", 2)), ("c", ("c", 3)))
println(
list1.map{
case (groupkey,(word,count))=>(word,count*2)
}
)
}
}
匹配对象及样例类
val user = User(“zhangsan”,11),该语句在执行时,实际调用的是 User 伴生对象中的apply 方法,因此不用 new 关键字就能构造出相应的对象。
当将 **User(“zhangsan”, 11)**写在 case 后时[case User(“zhangsan”, 11) => “yes”],会默认调用 unapply 方法(对象提取器),user 作为 unapply 方法的参数,unapply 方法将 user 对象的 name 和 age 属性提取出来,与 User(“zhangsan”, 11)中的属性值进行匹配
case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功,属性不一致,或返回 None,则匹配失败。
若只提取对象的一个属性,则提取器为 unapply(obj:Obj): Option[T]
若提取对象的多个属性,则提取器为 unapply(obj:Obj): Option[(T1,T2,T3…)]
若提取对象的可变个属性,则提取器为 unapplySeq(obj:Obj): Option[Seq[T]
package chapter08
object Test04_MatchObject {
def main(args: Array[String]): Unit = {
val student = new Student("alice", 18)
// 针对对象实例的内容进行匹配
val result = student match {
case Student("alice", 18) => "Alice, 18"
case _ => "Else"
}
println(result)
}
}
// 定义类
class Student(val name: String, val age: Int)
// 定义伴生对象
object Student {
def apply(name: String, age: Int): Student = new Student(name, age)
// 必须实现一个unapply方法,用来对对象属性进行拆解
def unapply(student: Student): Option[(String, Int)] = {
if (student == null){
None
} else {
Some((student.name, student.age))
}
}
}
样例类
语法:case class Person (name: String, age: Int)
样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如 apply、unapply、toString、equals、hashCode 和 copy。
样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。
构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)
object Test05_MatchCaseClass {
def main(args: Array[String]): Unit = {
val student = Student1("alice", 18)
// 针对对象实例的内容进行匹配
val result = student match {
case Student1("alice", 18) => "Alice, 18"
case _ => "Else"
}
println(result)
}
}
// 定义样例类
case class Student1(name: String, age: Int)
偏函数中的模式匹配 (了解)
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为 List[Int],而我们需要的是第一个元素是 0 的集合,这就是通过模式匹配实现的
注:该偏函数的功能是返回输入的 List 集合的第二个元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YzT07Zq8-1659264210160)(E:/%E5%AD%A6%E4%B9%A0/%E5%A4%A7%E6%95%B0%E6%8D%AE/scala/image-20220720230805653.png)]
上述代码会被 scala 编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数检查的函数——isDefinedAt,其返回值类型为 Boolean。
val second = new PartialFunction[List[Int], Option[Int]] {
//检查输入参数是否合格
override def isDefinedAt(list: List[Int]): Boolean = list match
{
case x :: y :: _ => true
case _ => false
}
//执行函数逻辑
override def apply(list: List[Int]): Option[Int] = list match
{
case x :: y :: _ => Some(y)
} }
偏函数使用
偏函数不能像 second(List(1,2,3))这样直接使用,因为这样会直接调用 apply 方法,而应
该调用 applyOrElse 方法,如下
second.applyOrElse(List(1,2,3), (_: List[Int]) => None)
applyOrElse 方法的逻辑为 if (ifDefinedAt(list)) apply(list) else default。如果输入参数满
足条件,即 isDefinedAt 返回 true,则执行 apply 方法,否则执行 defalut 方法,default 方法
为参数不满足要求的处理逻辑
package chapter08
object Test06_PartialFunction {
def main(args: Array[String]): Unit = {
val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))
// 1. map转换,实现key不变,value2倍
val newList = list.map( tuple => (tuple._1, tuple._2 * 2) )
// 2. 用模式匹配对元组元素赋值,实现功能
val newList2 = list.map(
tuple => {
tuple match {
case (word, count) => (word, count * 2)
}
}
)
// 3. 省略lambda表达式的写法,进行简化
val newList3 = list.map {
case (word, count) => (word, count * 2)
}
println(newList)
println(newList2)
println(newList3)
// 偏函数的应用,求绝对值
// 对输入数据分为不同的情形:正、负、0
val positiveAbs: PartialFunction[Int, Int] = {
case x if x > 0 => x
}
val negativeAbs: PartialFunction[Int, Int] = {
case x if x < 0 => -x
}
val zeroAbs: PartialFunction[Int, Int] = {
case 0 => 0
}
def abs(x: Int): Int = (positiveAbs orElse negativeAbs orElse zeroAbs) (x)
println(abs(-67))
println(abs(35))
println(abs(0))
}
}
异常处理
def main(args: Array[String]): Unit = {
try {
var n= 10 / 0
}catch {
case ex: ArithmeticException=>{
// 发生算术异常
println("发生算术异常")
}
case ex: Exception=>{
// 对异常处理
println("发生了异常 1")
println("发生了异常 2")
}
}finally {
println("finally")
}
}
-
我们将可疑代码封装在 try 块中。在 try 块之后使用了一个 catch 处理程序来捕获异常。如果发生任何异常,catch 处理程序将处理它,程序将不会异常终止。
-
Scala 的异常的工作机制和 Java 一样,但是 Scala 没有“checked(编译期)”异常, 即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
-
异常捕捉的机制与其他语言中一样,如果有异常发生,catch 子句是按次序捕捉的。因此,在 catch 子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在 Scala 中也不会报错,但这样是非常不好的编程风格。
-
finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和 Java 一样。
-
用 throw 关键字,抛出一个异常对象。所有异常都是 Throwable 的子类型。throw 表达式是有类型的,就是 Nothing,因为 Nothing 是所有类型的子类型,所以 throw 表达式可以用在需要类型的地方
def test():Nothing = { throw new Exception("不对") }
-
java 提供了 throws 关键字来声明异常。可以使用方法定义声明异常。它向调用者函数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在 try-catch块中,以避免程序异常终止。在 Scala 中,可以使用 throws 注解来声明异常
def main(args: Array[String]): Unit = { f11() } @throws(classOf[NumberFormatException]) def f11()={ "abc".toInt }
隐式转换
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译
隐式函数 implicit
使用 implicit 关键字声明的函数称之为隐式函数
隐式转换可以在不需改任何代码的情况下,扩展某个类的功能。
需求:通过隐式转化为 Int 类型增加方法
class MyRichInt(val self: Int) {
def myMax(i: Int): Int = {
if (self < i) i else self
}
def myMin(i: Int): Int = {
if (self < i) self else i
}
}
object Test02_Implicit {
def main(args: Array[String]): Unit = {
// 1. 隐式函数
implicit def convert(num: Int): MyRichInt = new MyRichInt(num)
println(12.myMax(15))
}
}
隐式参数
普通方法或者函数中的参数可以通过 implicit 关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。
(1)同一个作用域中,相同类型的隐式值只能有一个
(2)编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。
(3)隐式参数优先于默认参数
object Test02_Implicit {
def main(args: Array[String]): Unit = {
// 3. 隐式参数
implicit val str: String = "alice"
// implicit val str2: String = "alice2"
implicit val num: Int = 18
def sayHello()(implicit name: String): Unit = {
println("hello, " + name)
}
def sayHi(implicit name: String = "atguigu"): Unit = {
println("hi, " + name)
}
sayHello
sayHi
// 简便写法
def hiAge(): Unit = {
println("hi, " + implicitly[Int])
}
hiAge()
}
}
隐式类
在 Scala2.10 后提供了隐式类,可以使用 implicit 声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用。
(1)其所带的构造参数有且只能有一个
(2)隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。
object Test02_Implicit {
def main(args: Array[String]): Unit = {
// 2. 隐式类
implicit class MyRichInt2(val self: Int) {
// 自定义比较大小的方法
def myMax2(n: Int): Int = if ( n < self ) self else n
def myMin2(n: Int): Int = if ( n < self ) n else self
}
println(12.myMin2(15))
}
}
隐式解析
- 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)
- 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象
//(2)如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。
类型的作用域是指与该类型相关联的全部伴生模块,
object TestTransform extends PersonTrait {
def main(args: Array[String]): Unit = {
//(1)首先会在当前代码作用域下查找隐式实体
val teacher = new Teacher()
teacher.eat()
teacher.say()
}
class Teacher {
def eat(): Unit = {
println("eat...")
}
} }
trait PersonTrait {
}
object PersonTrait {
// 隐式类 : 类型 1 => 类型 2
implicit class Person5(user:Teacher) {
def say(): Unit = {
println("say...")
}
}
}
泛型
协变和逆变
class MyList[+T]{ //协变
}
class MyList[-T]{ //逆变
}
class MyList[T] //不变
说明
协变:Son 是 Father 的子类,则 MyList[Son] 也作为 MyList[Father]的“子类”。
逆变:Son 是 Father 的子类,则 MyList[Son]作为 MyList[Father]的“父类”。
不变:Son 是 Father 的子类,则 MyList[Father]与 MyList[Son]“无父子关系”。
// 定义继承关系
class Parent {}
class Child extends Parent {}
class SubChild extends Child {}
// 定义带泛型的集合类型 -E 逆变
class MyCollection[-E] {}
object Test03_Generics {
def main(args: Array[String]): Unit = {
// 1. 协变和逆变
val child: Parent = new Child
// val childList: MyCollection[Parent] = new MyCollection[Child]
val childList: MyCollection[SubChild] = new MyCollection[Child]
// 2. 上下限
def test[A <: Child](a: A): Unit = {
println(a.getClass.getName)
}
test[SubChild](new SubChild)
}
}
泛型上下限
泛型的上下限的作用是对传入的泛型进行限定。
Class PersonList[T <: Person]{ //泛型上限}
Class PersonList[T >: Person]{ //泛型下限}
class Parent{}
class Child extends Parent{}
class SubChild extends Child{}
object Scala_TestGeneric {
def main(args: Array[String]): Unit = {
//test(classOf[SubChild])
//test[Child](new SubChild)
}
//泛型通配符之上限
//def test[A <: Child](a:Class[A]): Unit ={
// println(a)
//}
//泛型通配符之下限
//def test[A >: Child](a:Class[A]): Unit ={
// println(a)
//}
//泛型通配符之下限 形式扩展
def test[A >: Child](a:A): Unit ={
println(a.getClass.getName)
}
}
上下文限定
语法
def f [ A : B ] (a: A) = println(a) //等同于 def f [ A ] (a:A)(implicit arg:B[A])=println(a)
说明
上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]]
获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误
implicit val x = 1
val y = implicitly[Int]
val z = implicitly[Double]
def f[A:Ordering](a:A,b:A) =implicitly[Ordering[A]].compare(a,b)
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
idea 快捷键
ctrl+X 删除行
ctrl+H 显示类的结构图(类的继承层次)
ctrl+enter 导入包,自动修正
ctrl+R 替换文本
ctrl+F 查找文本
ctrl+space 代码提示
ctrl+shift+space 自动补全代码
ctrl+alt+T 可以把代码包在一块内,例如: try/catch
ctrl+alt+V 可以引入变量。例如:new String();自动导入变量定义
ctrl+alt+I 将选中的代码进行自动缩进编排,这个功能在编辑jsp文件时也可以规则
ctrl+alt+O 优化导入的类和包
("hello, " + name)
}
def sayHi(implicit name: String = “atguigu”): Unit = {
println("hi, " + name)
}
sayHello
sayHi
// 简便写法
def hiAge(): Unit = {
println("hi, " + implicitly[Int])
}
hiAge()
}
}
### 隐式类
在 Scala2.10 后提供了隐式类,可以使用 **implicit** 声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用。
(1)其所带的构造参数有且只能有一个
(2)**隐式类必须被定义在“类”或“伴生对象”或“包对象”里**,即隐式类不能是**顶级的**。
```scala
object Test02_Implicit {
def main(args: Array[String]): Unit = {
// 2. 隐式类
implicit class MyRichInt2(val self: Int) {
// 自定义比较大小的方法
def myMax2(n: Int): Int = if ( n < self ) self else n
def myMin2(n: Int): Int = if ( n < self ) n else self
}
println(12.myMin2(15))
}
}
隐式解析
- 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)
- 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象
//(2)如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。
类型的作用域是指与该类型相关联的全部伴生模块,
object TestTransform extends PersonTrait {
def main(args: Array[String]): Unit = {
//(1)首先会在当前代码作用域下查找隐式实体
val teacher = new Teacher()
teacher.eat()
teacher.say()
}
class Teacher {
def eat(): Unit = {
println("eat...")
}
} }
trait PersonTrait {
}
object PersonTrait {
// 隐式类 : 类型 1 => 类型 2
implicit class Person5(user:Teacher) {
def say(): Unit = {
println("say...")
}
}
}
泛型
协变和逆变
class MyList[+T]{ //协变
}
class MyList[-T]{ //逆变
}
class MyList[T] //不变
说明
协变:Son 是 Father 的子类,则 MyList[Son] 也作为 MyList[Father]的“子类”。
逆变:Son 是 Father 的子类,则 MyList[Son]作为 MyList[Father]的“父类”。
不变:Son 是 Father 的子类,则 MyList[Father]与 MyList[Son]“无父子关系”。
// 定义继承关系
class Parent {}
class Child extends Parent {}
class SubChild extends Child {}
// 定义带泛型的集合类型 -E 逆变
class MyCollection[-E] {}
object Test03_Generics {
def main(args: Array[String]): Unit = {
// 1. 协变和逆变
val child: Parent = new Child
// val childList: MyCollection[Parent] = new MyCollection[Child]
val childList: MyCollection[SubChild] = new MyCollection[Child]
// 2. 上下限
def test[A <: Child](a: A): Unit = {
println(a.getClass.getName)
}
test[SubChild](new SubChild)
}
}
泛型上下限
泛型的上下限的作用是对传入的泛型进行限定。
Class PersonList[T <: Person]{ //泛型上限}
Class PersonList[T >: Person]{ //泛型下限}
class Parent{}
class Child extends Parent{}
class SubChild extends Child{}
object Scala_TestGeneric {
def main(args: Array[String]): Unit = {
//test(classOf[SubChild])
//test[Child](new SubChild)
}
//泛型通配符之上限
//def test[A <: Child](a:Class[A]): Unit ={
// println(a)
//}
//泛型通配符之下限
//def test[A >: Child](a:Class[A]): Unit ={
// println(a)
//}
//泛型通配符之下限 形式扩展
def test[A >: Child](a:A): Unit ={
println(a.getClass.getName)
}
}
上下文限定
语法
def f [ A : B ] (a: A) = println(a) //等同于 def f [ A ] (a:A)(implicit arg:B[A])=println(a)
说明
上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]]
获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误
implicit val x = 1
val y = implicitly[Int]
val z = implicitly[Double]
def f[A:Ordering](a:A,b:A) =implicitly[Ordering[A]].compare(a,b)
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
idea 快捷键
ctrl+X 删除行
ctrl+H 显示类的结构图(类的继承层次)
ctrl+enter 导入包,自动修正
ctrl+R 替换文本
ctrl+F 查找文本
ctrl+space 代码提示
ctrl+shift+space 自动补全代码
ctrl+alt+T 可以把代码包在一块内,例如: try/catch
ctrl+alt+V 可以引入变量。例如:new String();自动导入变量定义
ctrl+alt+I 将选中的代码进行自动缩进编排,这个功能在编辑jsp文件时也可以规则
ctrl+alt+O 优化导入的类和包