18scala笔记

news2024/11/24 23:12:55

Scala2.12

视频地址

1 入门

1.1 发展历史

1.2 Scala 和 Java

Scala = Java++

  1. 编写代码
  2. 使用scalac编译成.class字节码文件
  3. scala + .class文件 执行代码

image-20230213150532260

1.3 特点

image-20230213150810221

1.4 安装

视频地址

注意配置好环境变量

简单代码

image-20230213152120101

1.5 编译文件

编译scala文件会产生两个.class文件

image-20230213152410818

使用java命令执行scala编译出的class文件,报错:

image-20230213152629543

正确方法如下

image-20230213153254448

1.6 IDEA使用

创建maven项目

image-20230213153900096

安装scala插件

image-20230213154308682

创建java 和scala目录同时设置成源代码目录

image-20230213154551564

右击项目名 -> 添加框架支持

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.7 HelloWorld

创建包和类 输入main即可自动补全

image-20230213155506439

基本格式

image-20230213155742597

1.8 class 和 object

static关键字 调用方法:类名.属性

// 在java中 static修饰的字段是属于类的,也就是所有创建的对象都会有这个属性
public class Student {
    private String name;
    private Integer age;
    private static String school = "atguigu";

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public void printInfo(){
        System.out.println(this.name + " " + this.age + " " + Student.school);
    }

    public static void main(String[] args) {
        Student alice = new Student("alice", 20);
        Student bob = new Student("bob", 23);
        alice.printInfo();
        bob.printInfo();
    }
}

而在scala中,使用伴生对象代替了static字段,也就是类存在,伴生对象就存在

class Student(name: String, age: Int) {
  def printInfo(): Unit = {
    println(name + " " + age + " " + Student.school)
  }
}

// 引入伴生对象
object Student{
  val school: String = "atguigu"

  def main(args: Array[String]): Unit = {
    val alice = new Student("alice", 20)
    val bob = new Student("bob", 23)

    alice.printInfo()
    bob.printInfo()
  }
}

image-20230213165715060

1.9 反编译

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2 变量和数据类型

2.1 注释

和java完全一样

2.2 变量和常量

image-20230213170334750

原则

  1. 声明变量时,类型可以省略,编译器自动推导,即类型推导,var a1 = 10
  2. 类型确定后,就不能修改,说明Scala是强数据类型语言
  3. 变量声明时,必须要有初始值
  4. 在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改。

2.3 命名规范

基本与Java一致

  1. 以字母或者下划线开头,后接字母、数字、下划线
  2. 以操作符开头,且只包含操作符(+ - * / # !等)
  3. 用反引号`…`包括的任意字符串,即使是 Scala 关键字(39 个)也可以

关键字

image-20230213202819315

2.4 字符串输出

基本用法

  1. 字符串通过+连接
  2. printf 用法:字符串,通过%传值。
  3. 字符串模板(插值字符串):通过$获取变量值
var name: String = "jinlian"
 var age: Int = 18
 //(1)字符串,通过+号连接
 println(name + " " + age)
//(2)printf 用法字符串,通过%传值。
 printf("name=%s age=%d\n", name, age)
//(3)字符串,通过$引用
/*多行字符串,在 Scala中,利用三个双引号包围多行字符串就可以实现。输入的内容,带有空格、\t 之类,导致每一行的开始位置不能整洁对齐。应用 scala 的 stripMargin 方法,在 scala 中 stripMargin 默认是“|”作为连接符,在多行换行的行头前面加一个“|”符号即可。
*/
val s =
 """
     |select
     |name,
     |age
     |from user
     |where name="zhangsan"
 """.stripMargin
println(s)

//如果需要对变量进行运算,那么可以加${}
val s1 =
 s"""
     |select
     | name,
     | age
     |from user
     |where name="$name" and age=${age+2}
 """.stripMargin
 println(s1)

val s2 = s"name=$name"
 println(s2)

image-20230213203651814

2.5 键盘输入

基本用法

  • StdIn.readLine()

  • StdIn.readShort()

  • StdIn.readDouble()

    // 1 输入姓名
    println("input name:")
    var name = StdIn.readLine()
    // 2 输入年龄
    println("input age:")
    var age = StdIn.readShort()
    // 3 输入薪水
    println("input sal:")
    var sal = StdIn.readDouble()
    // 4 打印
    println("name=" + name)
    println("age=" + age)
    println("sal=" + sal)

image-20230213203914967

2.6 数字类型

Java

image-20230213204030994

scala

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.7 整数类型

Byte、Short、Int、Long

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

定义不要超过类型的范围

// 正确
 var n1:Byte = 127
 var n2:Byte = -128
 // 错误
 // var n3:Byte = 128
 // var n4:Byte = -129

Scala 的整型,默认为 Int 型,声明 Long 型,须后加‘l’或‘L’

var n5 = 10
println(n5)
var n6 = 9223372036854775807L
println(n6)

Scala 程序中变量常声明为 Int 型,除非不足以表示大数,才使用 Long

2.8 浮点类型

Scala 的浮点类型可以表示一个小数,比如 123.4f,7.8,0.12 等等。

image-20230213205125576

Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加‘f’或‘F’。

// 建议,在开发中需要高精度小数时,请选择 Double
 var n7 = 2.2345678912f
 var n8 = 2.2345678912

image-20230213205227428

2.9 字符类型

字符类型可以表示单个字符,字符类型是 Char。

  1. 字符常量是用单引号 ’ ’ 括起来的单个字符。
  2. \t :一个制表位,实现对齐的功能
  3. \n :换行符
  4. \\ :表示\
  5. \" :表示"

虽然IDEA会报错 但是默认会自动进行强制类型转换

image-20230213205758743

这样写就会报错

image-20230213205845062

2.10 Boolean类型

同Java

image-20230213205927862

2.11 Unit 、Null 、 Nothing

image-20230213210100741

Unit

image-20230213210324788

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 是其他任意类型的子类,他还能跟要求返回值的方法兼容。

 def main(args: Array[String]): Unit = {
     def test() : Nothing={
         throw new Exception()
      }
         test()
  }

image-20230213210823299

2.12 类型转换

基本同Java

object TestValueTransfer {
 def main(args: Array[String]): Unit = {
 //(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有
数据转换成精度大的那种数值类型,然后再进行计算。
 var n = 1 + 2.0
 println(n) // n 就是 Double
 //(2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就
会进行自动类型转换。
 var n2 : Double= 1.0
 //var n3 : Int = n2 //错误,原因不能把高精度的数据直接赋值和低
精度。
 //(3)(byte,short)和 char 之间不会相互自动转换。
 var n4 : Byte = 1
 //var c1 : Char = n4 //错误
 var n5:Int = n4
 //(4)byte,short,char 他们三者可以计算,在计算时首先转换为 int
类型。
 var n6 : Byte = 1
 var c2 : Char = 1
 // var n : Short = n6 + c2 //当 n6 + c2 结果类型就是 int
 // var n7 : Short = 10 + 90 //错误
 }
}

强制类型转换

Java : int num = (int)2.5
Scala : var num : Int = 2.7.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

数值类型与String类型转换

  1. 基本类型转 String 类型(语法:将基本类型的值+“” 即可)
  2. String 类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)

def main(args: Array[String]): Unit = {
  var n: Int = 130
  var b: Byte = n.toByte
  println(b) //-126 原因:Byte最大值为127,-128 -127 -126
}

3 运算符

大体同Java

3.1 注意事项

Scala:==更加类似于 Java 中的 equals

def main(args: Array[String]): Unit = {
 val s1 = "abc"
 val s2 = new String("abc")
 println(s1 == s2)
println(s1.eq(s2))
}
输出结果:
true
false

Scala 中没有++--操作符,可以通过+=-=来实现同样的效果

3.2 运算符本质

在 Scala 中其实是没有运算符的,所有运算符都是方法。

  1. 当调用对象的方法时,点.可以省略
  2. 如果函数参数只有一个,或者没有参数,()可以省略
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)
 }
}

4 流程控制

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)
 }
}

Java 中的三元运算符可以用 if else 实现

如果大括号{}内的逻辑代码只有一行,大括号可以省略。如果省略大括号,if 只对最近 的一行逻辑代码起作用。

object TestIfElse {
 def main(args: Array[String]): Unit = {
 // Java
// int result = flag?1:0
 // Scala
 println("input age")
 var age = StdIn.readInt()
 val res:Any = if (age < 18) "童年" else "成年"
 println(res)
 }
}

4.1 Switch 分支结构

在 Scala 中没有 Switch,而是使用模式匹配来处理

4.2 For循环控制

基本用法 to

// i 将会从 1-3 循环,前后闭合
for(i <- 1 to 3){
 print(i + " ")
}
i 1 2 3

Until

// 前闭合后开
for(i <- 1 until 3) {
 print(i + " ")
}
i 1 2

4.3 循环守卫

for(i <- 1 to 3 if i != 2) {
 print(i + " ")
}
println()
// 等价于 =============
for (i <- 1 to 3){
if (i != 2) {
    print(i + " ")
}
}

4.4 循环步长

// by 表示步长
for (i <- 1 to 10 by 2) {
 println("i=" + i)
}
i 1 3 5 7 9

4.5 嵌套循环

// 没有关键字,所以范围后一定要加;来隔断逻辑
for(i <- 1 to 3; j <- 1 to 3) {
 println(" i =" + i + " j = " + j)
}
// 等价于===============================
for (i <- 1 to 3) {
 for (j <- 1 to 3) {
 println("i =" + i + " j=" + j)
 }
}

4.6 引入变量

for(i <- 1 to 3; j = 4 - i) {
 println("i=" + i + " j=" + j)
}
==================================
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)
}
// 三者等价

4.7 循环返回值 yield

类似于js中的map

// 将遍历过程中处理的结果返回到一个新 Vector 集合中
val res = for(i <- 1 to 10) yield i
println(res)
// 结果:res = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// 将原数据中所有值乘以 2,并把数据返回到一个新的集合中
var res = for(i <-1 to 10) yield {
 	i * 2
 }
// 结果:res = Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

4.8 倒序打印 reverse

for(i <- 1 to 10 reverse){
 println(i)
}

4.9 While循环控制

  1. 与 for 语句不同,while 语句没有返回值,即整个 while 语句的结果是 Unit 类型()
  2. 因为 while 中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量,而变量需要声明在 while 循环的外部,那么就等同于循环的内部对外部的变量 造成了影响,所以不推荐使用,而是推荐使用 for 循环。

显然,while循环不能满足大数据并行处理的要求,因为他们公用的都是外部的变量,比如控制循环的i

def main(args: Array[String]): Unit = {
 var i = 0
 //变量必须在while外面声明
 while (i < 10) {
 println("宋宋,喜欢海狗人参丸" + i)
 	i += 1
 }
}

4.10 循环中断

Scala 内置控制结构特地去掉了 break 和 continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。Scala中使用breakable 控制结构来实现 break continue 功能。

采用异常的方式退出循环

def main(args: Array[String]): Unit = {
    try {
      for (elem <- 1 to 10) {
        println(elem)
        if (elem == 5) throw new RuntimeException
      }
    } catch {
      case e =>
    }
    println("正常结束循环")
}

采用 Scala 自带的函数,退出循环

import scala.util.control.Breaks
def main(args: Array[String]): Unit = {
 Breaks.breakable(
     for (elem <- 1 to 10) {
         println(elem)
         if (elem == 5) Breaks.break()
     }
 )
 println("正常结束循环")
}

对 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("正常结束循环")
 }
}

5 函数式编程

面向对象编程

解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题。

  • 对象:用户

  • 行为:登录、连接 JDBC、读取数据库

  • 属性:用户名、密码

Scala 语言是一个完全面向对象编程语言。

万物皆对象

对象的本质:对数据行为的一个封装

函数式编程

解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。

例如:请求->用户名、密码->连接 JDBC->读取数据库

Scala 语言是一个完全函数式编程语言。万物皆函数

函数的本质:函数可以当做一个值进行传递

5.1 函数基础

5.1.1 函数基本语法

image-20230214132525910

// 定义一个函数,实现将传入的名称打印出来。
object TestFunction {
def main(args: Array[String]): Unit = {
 // (1)函数定义
 def f(arg: String): Unit = {
 	println(arg)
 }
 // (2)函数调用
 // 函数名(参数)
 f("hello world")
 }
}

5.1.2 函数和方法的区别

概念

  1. 为完成某一功能的程序语句的集合,称为函数。
  2. 类中的函数称之方法。

案例实操

  1. Scala 语言可以在任何的语法结构中声明任何的语法
  2. 函数没有重载和重写的概念;方法可以进行重载重写
  3. Scala 中函数可以嵌套定义
// (2)方法可以进行重载和重写,程序可以执行
def main(): Unit = {
}

def main(args: Array[String]): Unit = {
// (1)Scala 语言可以在任何的语法结构中声明任何的语法
import java.util.Date
new Date()

// (2)函数没有重载和重写的概念,程序报错
def test(): Unit = {
  println("无参,无返回值")
}
test()
def test(name: String): Unit = {
  println()
}

//(3)Scala 中函数可以嵌套定义
def test2(): Unit = {
  def test3(name: String): Unit = {
    println("函数可以嵌套定义")
  }
}
}
5.1.3 函数定义
 def main(args: Array[String]): Unit = {
 // 函数 1:无参,无返回值
 def test1(): Unit ={
 	println("无参,无返回值")
 }
 test1()
 // 函数 2:无参,有返回值
 def test2():String={
 	return "无参,有返回值"
 }
 println(test2())
 // 函数 3:有参,无返回值
 def test3(s:String):Unit={
 	println(s)
 }
 test3("jinlian")
 // 函数 4:有参,有返回值
 def test4(s:String):String={
 	return s+"有参,有返回值"
 }
 println(test4("hello "))
 // 函数 5:多参,无返回值
 def test5(name:String, age:Int):Unit={
 	println(s"$name, $age")
 }
 test5("dalang",40)
 }
}
5.1.4 参数函数
  1. 可变参数
  2. 如果参数列表中存在多个参数,那么可变参数一般放置在最后
  3. 参数默认值,一般将有默认值的参数放置在参数列表的后面
  4. 带名参数
object TestFunction {
 def main(args: Array[String]): Unit = {
 // (1)可变参数
 def test( s : String* ): Unit = {
 	println(s)
 }
 // 有输入参数:输出 Array
 test("Hello", "Scala")
 // 无输入参数:输出 List()
 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")
 }
}

5.1.5 函数至简原则

能省就省

  1. return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
  2. 如果函数体只有一行代码,可以省略花括号
  3. 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
  4. 如果有 return,则不能省略返回值类型,必须指定
  5. 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
  6. Scala 如果期望是无返回值类型,可以省略等号
  7. 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
  8. 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
  9. 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
object TestFunction {
 def main(args: Array[String]): Unit = {
 // (0)函数标准写法
 def f( s : String ): String = {
 	return s + " jinlian"
 }
 println(f("Hello"))
 // 至简原则:能省则省
 //(1) return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
 def f1( s : String ): String = {
 	s + " jinlian"
 }
 println(f1("Hello"))
 //(2)如果函数体只有一行代码,可以省略花括号
 def f2(s:String):String = s + " jinlian"
 //(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
 def f3( s : String ) = s + " jinlian"
 println(f3("Hello3"))
 //(4)如果有 return,则不能省略返回值类型,必须指定。
 def f4() :String = {
 	return "ximenqing4"
 }
 println(f4())
 //(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
 def f5(): Unit = {
 	return "dalang5"
 }
 println(f5())
 //(6)一般不使用 Scala 如果期望是无返回值类型,可以省略等号
 // 将无返回值的函数称之为过程
 def f6() {
 "dalang6"
 }
 println(f6())  //输出值为(),因为没有等号 是无返回值类型
 //(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
 def f7() = "dalang7"
 println(f7())
 println(f7)
 //(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
 def f8 = "dalang"
 //println(f8())
 println(f8)
 //(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
 //这就是匿名函数 lambda表达式
 (x:String)=>{println("wusong")}
 
}

5.2 函数高级

5.2.1 高阶函数

函数作为参数输入

image-20230214144049585

_的用法

(3) f中传入一个函数,函数的参数为name,方法体为输出name

(4) 可以知道f中传入的函数就是将一个参数输出,无论参数的名字叫什么,所以可以用_代替参数名

image-20230410141509421

    // 2. 函数作为参数进行传递
    // 定义二元计算函数
    def dualEval(op: (Int, Int)=>Int, a: Int, b: Int): Int = {
      op(a, b)
    }

    def add(a: Int, b: Int): Int = {
      a + b
    }

    println(dualEval(add, 12, 35))
    println(dualEval((a, b) => a + b, 12, 35))
    println(dualEval(_ + _, 12, 35))

函数作为值进行传递,即一个函数等于另外一个函数,f2 = f _表示f2这个函数赋值为f

image-20230214150629164

image-20230214150702524

函数作为函数的返回值返回

// 函数f5的参数为空,它的返回值为一个输入参数为Int,返回值为空的函数
def f5(): Int=>Unit = {
  def f6(a: Int): Unit = {
    println("f6调用 " + a)
  }
  f6    // 将函数直接返回
}
// 调用函数,相当于f5()返回的是一个函数名,后面的(25)是参数
f5()(25)
5.2.2 匿名函数
(x:Int)=>{函数体}

至简原则

  • 参数的类型可以省略,会根据形参进行自动的推导

image-20230214144655828

  • 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。

image-20230214144715540

  • 匿名函数如果只有一行,则大括号也可以省略

image-20230214144753196

  • 如果参数只出现一次,则参数省略且后面参数可以用_代替

image-20230214144848182

  • 如果执行的是一步操作,那么可以直接省略_

image-20230214145019165

image-20230214145356526

简化:

image-20230214145641473

5.2.3 实战

对数组进行操作,定义运算函数

  def main(args: Array[String]): Unit = {
    val arr: Array[Int] = Array(12, 45, 75, 98)

    // 对数组进行处理,将操作抽象出来,处理完毕之后的结果返回一个新的数组
    def arrayOperation(array: Array[Int], op: Int=>Int): Array[Int] = {
      for (elem <- array) yield op(elem)
    }
    // 定义一个加一操作
    def addOne(elem: Int): Int = {
      elem + 1
    }
    // 调用函数
    val newArray: Array[Int] = arrayOperation(arr, addOne)
	// 将数组中的元素取出来 并用,分隔
    println(newArray.mkString(","))

    // 传入匿名函数,实现元素翻倍
    val newArray2 = arrayOperation(arr, _ * 2)
    // 将数组中的元素取出来 并用,分隔
    println(newArray2.mkString(", "))
  }

image-20230214152813936

定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。要求调用函数 fun(0, “”, ‘0’)得到返回值为 false,其它情况均返回 true。

    val fun = (i: Int, s: String, c: Char) => {
      if (i == 0 && s == "" && c == '0') false else true
    }

    println(fun(0, "", '0'))
    println(fun(0, "", '1'))
    println(fun(23, "", '0'))
    println(fun(0, "hello", '0'))

    println("===========================")

定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。 它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接 收一个 Char 类型的参数,返回一个 Boolean 的值。 要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true。

    def func(i: Int): String=>(Char=>Boolean) = {
      def f1(s: String): Char=>Boolean = {
        def f2(c: Char): Boolean = {
          if (i == 0 && s == "" && c == '0') false else true
        }
        f2
      }
      f1
    }

    println(func(0)("")('0'))
    println(func(0)("")('1'))
    println(func(23)("")('0'))
    println(func(0)("hello")('0'))
 
    // 匿名函数简写
	// 匿名函数接收String类型的参数s,返回值为一个函数
	// 匿名函数接收Char类型的参数c,返回值为Boolean
    def func1(i: Int): String => (Char => Boolean) = {
      s => {
        c => {
          if (i == 0 && s == "" && c == '0') false else true
        }
      }
    }
// =======================省去括号就变成下面的样子
    def func1(i: Int): String=>(Char=>Boolean) = {
      s => c => if (i == 0 && s == "" && c == '0') false else true
    }

    println(func1(0)("")('0'))
    println(func1(0)("")('1'))
    println(func1(23)("")('0'))
    println(func1(0)("hello")('0'))

    // 柯里化
    def func2(i: Int)(s: String)(c: Char): Boolean = {
      if (i == 0 && s == "" && c == '0') false else true
    }
    println(func2(0)("")('0'))
    println(func2(0)("")('1'))
    println(func2(23)("")('0'))
    println(func2(0)("hello")('0'))
  }
5.2.4 函数柯里化和闭包

闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包

原理: scala是面向对象的,所有的方法都有一个地址,即:在堆中保存了下来,所以即使先执行的方法出栈了,但其数据任然保存在堆中,后面执行的方法可以访问到数据

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))
5.2.5 递归

同Java

5.2.6 控制抽象

值调用:参数传值,平常的传值方法

def foo(a: Int):Unit = {
 println(a) 
 }

名调用:传名参数,把代码块传递过去,有返回值

     def f1(): Int = {
      println("f1调用")
      12
    }
// 2. 传名参数,传递的不再是具体的值,而是代码块
    def f2(a: =>Int): Unit = {
      println("a: " + a)
      println("a: " + a)
    }
	// 传递的是f1这个代码块,相当于f2中的参数a=f1()
    f2(f1())

    f2({
      println("这是一个代码块")
      29
    })

image-20230214170241120

案例:自定义while循环

使用柯里化最容易理解,while(代码块)(代码块)

package chapter05

object Test12_MyWhile {
  def main(args: Array[String]): Unit = {
    var n = 10

    // 1. 常规的while循环
    while (n >= 1){
      println(n)
      n -= 1
    }
    // 2. 用闭包实现一个函数,将代码块作为参数传入,递归调用
    def myWhile(condition: =>Boolean): (=>Unit)=>Unit = {
      // 内层函数需要递归调用,参数就是循环体
      def doLoop(op: =>Unit): Unit = {
        if (condition){
          op
          myWhile(condition)(op)
        }
      }
      doLoop _
    }
    println("=================")
    n = 10
    myWhile(n >= 1){
      println(n)
      n -= 1
    }
    // 3. 用匿名函数实现
    def myWhile2(condition: =>Boolean): (=>Unit)=>Unit = {
      // 内层函数需要递归调用,参数就是循环体
      op => {
        if (condition){
          op
          myWhile2(condition)(op)
        }
      }
    }
    println("=================")
    n = 10
    myWhile2(n >= 1){
      println(n)
      n -= 1
    }
    // 3. 用柯里化实现====最容易理解
    def myWhile3(condition: =>Boolean)(op: =>Unit): Unit = {
      if (condition){
        op
        myWhile3(condition)(op)
      }
    }
    println("=================")
    n = 10
    myWhile3(n >= 1){
      println(n)
      n -= 1
    }
  }
}

5.2.7 惰性加载

当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函 数才会执行。这种函数我们称之为惰性函数。

def main(args: Array[String]): Unit = {
 lazy val res = sum(10, 30)
 println("----------------")
 println("res=" + res)
}
def sum(n1: Int, n2: Int): Int = {
 println("sum 被执行。。。")
 return n1 + n2
}

image-20230214175308647

6 面向对象

Scala 的面向对象思想和 Java 的面向对象思想和概念是一致的。

Scala 中语法和 Java 不同,补充了更多的功能。

6.1 Scala包

6.1.1 包的命名

同java

6.1.2 包语句

有两种风格

  1. com.jaken.scala
  2. 嵌套风格
package com{
    package jaken{
        package scala{
        }
    }
}

优点:

  1. 一个源文件中可以声明多个 package
  2. 子包中的类可以直接访问父包中的内容,而无需导包
package com {
     import com.atguigu.Inner //父包访问子包需要导包
     object Outer {
     	 val out: String = "out"
         def main(args: Array[String]): Unit = {
             println(Inner.in)
         }
     }
     package atguigu {
     	object Inner {
   	  		val in: String = "in"
   	  		def main(args: Array[String]): Unit = {
     		println(Outer.out) //子包访问父包无需导包
     		}
     	}
     }
    }
    package other {
}
6.1.3 包对象

在 Scala 中可以为每个包定义一个同名的包对象(package object),定义在包对象中的成员,作为其对应包下所有 class 和 object 的共享变量,可以被直接访问

package object com{
    val shareValue="share"
    def shareMethod()={}
}


package com {
 object Outer {
 val out: String = "out"
     def main(args: Array[String]): Unit = {
        // 可以直接拿到包对象
     	println(shareValue)
     }
 }
}
6.1.4 导包说明

同java

6.2 类和对象

:可以看成一个模板

对象:表示具体的事物

6.2.1 定义类和属性

Scala 中没有 public,个.scala 中可以写个类。

  1. Scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是 public)
  2. 一个 Scala 源文件可以包含多个类
class Person {
 var name: String = "bobo" //定义属性
 var age: Int = _ // _表示给属性一个默认值,Int默认为0,String为null
 //Bean 属性(@BeanProperty),修饰的属性有getter/setter
 @BeanProperty 
 var sex: String = "男"

}
object Person {
 def main(args: Array[String]): Unit = {
 var person = new Person()
 println(person.name)
 person.setSex("女")
 println(person.getSex)
 }
}

6.3 封装

Scala 中的 public 属性,底层实际为 private,并通过 get 方法(obj.field())和 set 方法 (obj.field_=(value))对其进行操作。

所以 Scala 并不推荐将属性设为 private,再为其设置 public 的 get 和 set 方法的做法。但由于很多 Java 框架都利用反射调用 getXXX 和 setXXX 方 法,有时候为了和这些框架兼容,也会为 Scala 的属性设置 getXXX 和 setXXX 方法(通过 @BeanProperty 注解实现)。

6.3.1 访问权限
  1. Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字。
  2. private 为私有权限,只在类的内部和伴生对象中可用。
  3. protected 为受保护权限,Scala 中受保护权限比 Java 中更严格同类子类可以访问,同包无法访问
  4. private[包名]增加包访问权限,包名下的其他类也可以使用

image-20230410161637503

在子类中idCard由于是私有属性 所以不能访问

image-20230410161956170

在实例对象中,protect对象不能访问

image-20230410162129222

6.3.2 构造方法
object Test05_Constructor {
  def main(args: Array[String]): Unit = {
      // 调用的主构造方法
    val student1 = new Student1
      //调用的一般方法
    student1.Student1()
	//调用的辅助构造方法1
    val student2 = new Student1("alice")
      //调用的辅助构造方法2
    val student3 = new Student1("bob", 25)
  }
}

// 定义一个类,其实也是主构造方法
class Student1() {
  // 定义属性
  var name: String = _
  var age: Int = _

  println("1. 主构造方法被调用")

  // 声明辅助构造方法1
  def this(name: String) {
    // 首先必须调用主构造器
    this()    
    println("2. 辅助构造方法一被调用")
    this.name = name
    println(s"name: $name age: $age")
  }
  // 声明辅助构造方法2
  def this(name: String, age: Int){
    //调用构造方法1
    this(name)
    println("3. 辅助构造方法二被调用")
    this.age = age
    println(s"name: $name age: $age")
  }
// 不同于JAVA,这并不是一个构造方法,而是一个普通的方法
  def Student1(): Unit = {
    println("一般方法被调用")
  }
}

运行结果

image-20230413204137491

6.3.3构造方法参数

image-20230413204426920

实操

object Test06_ConstructorParams {
  def main(args: Array[String]): Unit = {
    val student2 = new Student2
    student2.name = "alice"
    student2.age = 18
    println(s"student2: name = ${student2.name}, age = ${student2.age}")

    val student3 = new Student3("bob", 20)
    println(s"student3: name = ${student3.name}, age = ${student3.age}")

    val student4 = new Student4("cary", 25)
      //由于Student4的参数未修饰,所以参数是局部变量而不是成员属性
//    println(s"student4: name = ${student4.name}, age = ${student4.age}")
    student4.printInfo()

    val student5 = new Student5("bob", 20)
    println(s"student3: name = ${student5.name}, age = ${student5.age}")

    student3.age = 21

    val student6 = new Student6("cary", 25, "atguigu")
    println(s"student6: name = ${student6.name}, age = ${student6.age}")
    student6.printInfo()
  }
}

// 定义类
// 无参构造器
class Student2 {
  // 单独定义属性
  var name: String = _
  var age: Int = _
}

// 上面定义等价于
class Student3(var name: String, var age: Int)

// 主构造器参数无修饰,name和age属性就相当于局部变量,而不是成员属性
class Student4(name: String, age: Int){
  def printInfo(){
    println(s"student4: name = ${name}, age = $age")
  }
}

//class Student4(_name: String, _age: Int){
//  var name: String = _name
//  var age: Int = _age
//}
//用常量修饰
class Student5(val name: String, val age: Int)
//用变量修饰
class Student6(var name: String, var age: Int){
  var school: String = _

  def this(name: String, age: Int, school: String){
    this(name, age)
    this.school = school
  }

  def printInfo(){
    println(s"student6: name = ${name}, age = $age, school = $school")
  }
}

运行结果

image-20230413205050433

6.4 继承

image-20230413205200392
object Test07_Inherit {
  def main(args: Array[String]): Unit = {
    val student1: Student7 = new Student7("alice", 18)
    val student2 = new Student7("bob", 20, "std001")

    student1.printInfo()
    student2.printInfo()

    val teacher = new Teacher
    teacher.printInfo()

    def personInfo(person: Person7): Unit = {
      person.printInfo()
    }

    println("=========================")

    val person = new Person7
    personInfo(student1)
    personInfo(teacher)
    personInfo(person)
  }
}

// 定义一个父类
class Person7() {
  var name: String = _
  var age: Int = _

  println("1. 父类的主构造器调用")

  def this(name: String, age: Int){
    this()
    println("2. 父类的辅助构造器调用")
    this.name = name
    this.age = age
  }

  def printInfo(): Unit = {
    println(s"Person: $name $age")
  }
}

// 定义子类
class Student7(name: String, age: Int) extends Person7(name, age) {
  var stdNo: String = _

  println("3. 子类的主构造器调用")

  def this(name: String, age: Int, stdNo: String){
    this(name, age)
    println("4. 子类的辅助构造器调用")
    this.stdNo = stdNo
  }

  override def printInfo(): Unit = {
    println(s"Student: $name $age $stdNo")
  }
}

class Teacher extends Person7 {
  override def printInfo(): Unit = {
    println(s"Teacher")
  }
}

image-20230413205553026

6.5 多态(动态绑定)

与java的区别

scala中的属性和方法都是动态绑定,也就是说,new的是什么,就调用什么的属性和方法

java中的属性是静态绑定方法是动态绑定,也就是说,定义为父类,而new子类,调用的属性是父类的属性值,调用的方法是子类的方法

java多态

public class TestDynamicBind {
    public static void main(String[] args) {
        Worker worker = new Worker();
        System.out.println(worker.name);
        worker.hello();
        worker.hi();

        System.out.println("===================");

        // 多态:定义的是Person父类,但new的是Worder子类
        Person person = new Worker();
        // 静态绑定属性,所以打印的是person的属性
        System.out.println(person.name); 
        // 动态绑定方法,所以打印的是worker的方法
        person.hello();    
        // 由于父类中没有hi方法,所以报错
//        person.hi();     // error
    }
}

class Person {
    String name = "person";
    public void hello() {
        System.out.println("hello person");
    }
}

class Worker extends Person {
    String name = "worker";
    public void hello() {
        System.out.println("hello worker");
    }
    public void hi() {
        System.out.println("hi worker");
    }
}

image-20230413211330954

scala多态

object Test08_DynamicBind {
  def main(args: Array[String]): Unit = {
      //同样定义的是父类,实例化的是子类
    val student: Person8 = new Student8
      //与JAVA不同,SCALA的属性也是动态绑定,所以是Student8的属性
    println(student.name)
      //调用的是是Student8的方法
    student.hello()
  }
}

class Person8 {
  val name: String = "person"
  def hello(): Unit = {
    println("hello person")
  }
}

class Student8 extends Person8 {
  override val name: String = "student"
  override def hello(): Unit = {
    println("hello student")
  }
}

image-20230413211402310

6.6 抽象类

基本语法

  1. 定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
  2. 定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
  3. 定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
package chapter06

object Test09_AbstractClass {
  def main(args: Array[String]): Unit = {
    val student = new Student9
    student.eat()
    student.sleep()
  }
}

// 定义抽象类
abstract class Person9{
  // 非抽象属性
  var name: String = "person"

  // 抽象属性
  var age: Int

  // 非抽象方法
  def eat(): Unit = {
    println("person eat")
  }

  // 抽象方法
  def sleep(): Unit
}

// 定义具体的实现子类
class Student9 extends Person9 {
  // 实现抽象属性,必须var,override可有可无
  var age: Int = 18
  // 实现抽象方法,直接定义就行,override可有可无
  def sleep(): Unit = {
    println("student sleep")
  }

  // 重写非抽象属性,因为父类name是var修饰的,直接写就可以
  // 如果父类是val,则子类都不可以修改,直接继承父类的
//  override val name: String = "student"
  name = "student"
	//重写非抽象方法
  override def eat(): Unit = {
    super.eat()
    println("student eat")
  }
}

image-20230413211732374

6.6.1 匿名抽象类
object Test10_AnnoymousClass {
  def main(args: Array[String]): Unit = {
      val person: Person10 = new Person10 {
      //有无override都可
      override var name: String = "alice"
      override def eat(): Unit = println("person eat")
    }
    println(person.name)
    person.eat()
  }
}

// 定义抽象类
abstract class Person10 {
  var name: String
  def eat(): Unit
}

6.7 单例对象(伴生对象)

Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。

基本语法

object Test11_Object {
  def main(args: Array[String]): Unit = {
//    val student = new Student11("alice", 18)
//    student.printInfo()
    // 实现构造方法私有化,也就是类.“静态”方法
    val student1 = Student11.newStudent("alice", 18)
    student1.printInfo()

    val student2 = Student11.apply("bob", 19)
    student2.printInfo()

    val student3 = Student11("bob", 19)
    student3.printInfo()
  }
}

// 定义类,private表明主构造器私有化了
class Student11 private(val name: String, val age: Int){
  def printInfo(): Unit = {
    // 注意这里的Student11.school,也就是类名.属性
    println(s"student: name = ${name}, age = $age, school = ${Student11.school}")
  }
}

// 伴生对象,可以访问伴生类的私有成员和方法
object Student11{
  //这个变量 就相当于java中的static修饰的静态变量
  val school: String = "atguigu"

  // 定义一个类的对象实例的创建方法,该方法也是静态方法
  def newStudent(name: String, age: Int): Student11 = new Student11(name, age)
  //如果参数名称是apply,则调用的时候可以省略类.apply(),直接写Student11("bob", 19)
  def apply(name: String, age: Int): Student11 = new Student11(name, age)
}

6.7.1 单例设计模式

也就是类只有一个,类中的属性先前就定义好了,写死了

object Test12_Singleton {
  def main(args: Array[String]): Unit = {
    val student1 = Student12.getInstance()
    student1.printInfo()
    val student2 = Student12.getInstance()
    student2.printInfo()
    // student1和student2的地址是相同的
    println(student1)
    println(student2)
  }
}

class Student12 private(val name: String, val age: Int){
  def printInfo(): Unit = {
    println(s"student: name = ${name}, age = $age, school = ${Student11.school}")
  }
}

// 饿汉式,无论Student12类是否存在,都会重新new
//object Student12 {
//  private val student: Student12 = new Student12("alice", 18)
//  def getInstance(): Student12 = student
//}

// 懒汉式,如果单例类不存在,才会new
object Student12 {
  // 相当于定义了静态变量student,它是Student12类型的,只有一份
  private var student: Student12 = _
  def getInstance(): Student12 = {
    if (student == null){
      // 如果没有对象实例的话,就创建一个
      student = new Student12("alice", 18)
    }
    student
  }
}

6.8 特质(trait)

某个类,它的本质,就用它的父类来体现,是继承关系。比如student类的本质就用它的父类person来体现。

motivation

然而,像student类,他会有young person或是old person,这两个类的特性是不一样的,在java中就定义了接口,使能够保持它的特性

scala中没有接口的概念,采用特质 trait(特征)来代替。Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。 Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种补充(有点类似于多继承的味道,但实际上还是为单继承)

基本语法

没有父类:class 类名 extends 特质 1 with 特质 2 with 特质 3

有父类:class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3

package chapter06

object Test13_Trait {
  def main(args: Array[String]): Unit = {
    val student: Student13 = new Student13
    student.sayHello()
    student.study()
    student.dating()
    student.play()
  }
}

// 定义一个父类
class Person13 {
  val name: String = "person"
  var age: Int = 18
  def sayHello(): Unit = {
    println("hello from: " + name)
  }
  def increase(): Unit = {
    println("person increase")
  }
}

// 定义一个特质
trait Young {
  // 声明抽象和非抽象属性
  var age: Int
  val name: String = "young"
  // 声明抽象和非抽象的方法
  def play(): Unit = {
    println(s"young people $name is playing")
  }
  def dating(): Unit
}

class Student13 extends Person13 with Young {
  // Person13中有name=person,Yong中有name=young,需要重写冲突的属性
  override val name: String = "student"
  // 实现抽象方法
  def dating(): Unit = println(s"student $name is dating")
  def study(): Unit = println(s"student $name is studying")
  // 重写父类方法
  override def sayHello(): Unit = {
    super.sayHello()
    println(s"hello from: student $name")
  }
}

image-20230413222947704

6.8.1 多特质(mixin)动态混入
package chapter06

object Test14_TraitMixin {
  def main(args: Array[String]): Unit = {
    val student = new Student14
    student.study()
    student.increase()
    student.play()
    student.increase()
    student.dating()
    student.increase()

    println("===========================")
    // 动态混入,也就是要什么特性就给什么特质
    val studentWithTalent = new Student14 with Talent {
      override def dancing(): Unit = println("student is good at dancing")
      override def singing(): Unit = println("student is good at singing")
    }

    studentWithTalent.sayHello()
    studentWithTalent.play()
    studentWithTalent.study()
    studentWithTalent.dating()
    studentWithTalent.dancing()
    studentWithTalent.singing()
  }
}

// 再定义一个特质
trait Knowledge {
  var amount: Int = 0
  def increase(): Unit
}

trait Talent {
  def singing(): Unit
  def dancing(): Unit
}

class Student14 extends Person13 with Young with Knowledge {
  // 重写冲突的属性
  override val name: String = "student"

  // 实现抽象方法
  def dating(): Unit = println(s"student $name is dating")

  def study(): Unit = println(s"student $name is studying")

  // 重写父类方法
  override def sayHello(): Unit = {
    super.sayHello()
    println(s"hello from: student $name")
  }

  // 实现特质中的抽象方法
  override def increase(): Unit = {
    amount += 1
    println(s"student $name knowledge increased: $amount")
  }
}
6.8.2 特质的叠加

image-20230414111208932

由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。 冲突分为以下两种:

解决这类冲突问题,直接在类(Sub)中重写冲突方法。

image-20230414111305482

所谓的“钻石问题”,解决这类冲突问题,Scala 采用了特质叠加的策略。

package chapter06

object Test15_TraitOverlying {
  def main(args: Array[String]): Unit = {
    // 钻石问题特征叠加
    val myFootBall = new MyFootBall
    println(myFootBall.describe())
  }
}
// 定义球类特征
trait Ball {
  def describe(): String = "ball"
}
// 定义颜色特征
trait ColorBall extends Ball {
  var color: String = "red"
  override def describe(): String = color + "-" + super.describe()
}

// 定义种类特征
trait CategoryBall extends Ball {
  var category: String = "foot"
  override def describe(): String = category + "-" + super.describe()
}

// 定义一个自定义球的类
class MyFootBall extends CategoryBall with ColorBall {
  override def describe(): String = "my ball is a " + super[CategoryBall].describe()
}

image-20230414111358864

image-20230414112339085

案例中的 super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质, 即,MyClass 中的 super 指代 Color,Color 中的 super 指代 Category,Category 中的 super 指代 Ball

6.8.3 指定super

除了上面的推断外,也可以指定使用哪个父类的方法

image-20230414112810278

6.8.4 特质和抽象类的区别
  1. 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
  2. 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数, 而特质不行(有无参构造)。
6.8.5 特质自身类型

自身类型可实现依赖注入的功能。

object Test16_TraitSelfType {
  def main(args: Array[String]): Unit = {
    val user = new RegisterUser("alice", "123456")
    user.insert()
  }
}

// 用户类
class User(val name: String, val password: String)

trait UserDao {
  // 相当于依赖注入的效果 _只是一个通配符 表示UserDao会用到User,但它们没有继承关系
  _: User =>

  // 向数据库插入数据
  def insert(): Unit = {
    // 使用this来调用User
    println(s"insert into db: ${this.name}")
  }
}

// 定义注册用户类
class RegisterUser(name: String, password: String) extends User(name, password) with UserDao

6.9 扩展

6.9.1 类型检查和转换
  • obj.isInstanceOf[T]:判断 obj 是不是 T 类型。

  • obj.asInstanceOf[T]:将 obj 强转成 T 类型。

  • classOf 获取对象的类名。

6.9.2 枚举类和应用类
// 定义枚举类对象
object WorkDay extends Enumeration {
  // 1,2表示键,也就是底层的存储
  val MONDAY = Value(1, "Monday")
  val TUESDAY = Value(2, "TuesDay")
}

// 定义应用类对象
object TestApp extends App {
  println("app start")
  // type 起别名
  type MyString = String
  val a: MyString = "abc"
  println(a)
}

// 2. 测试枚举类,直接引用就行
println(WorkDay.MONDAY)

7 集合

  1. Scala 的集合有三大类:序列 Seq集 Set映射 Map,所有的集合都扩展自 Iterable 特质。
  2. 对于几乎所有的集合类,Scala 都同时提供了可变不可变的版本,分别位于以下两 个包(scala.collection.immutable scala.collection.mutable)
  3. Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而 不会对原对象进行修改。类似于 java 中的 String 对象
  4. 可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似 于 java 中 StringBuilder 对象

建议

在操作集合的时候,不可变用符号可变用方法

7.1 不可变集合继承图

image-20230414131705294

7.2 可变集合继承图

image-20230414132345419

7.3 数组 相当于JAVA中的List

7.3.1 不可变数组

并不是说数组的值不可以修改,而是指向该数组的地址是不变的

创建数组

val arr1 = new Array[Int](10)
// 这里调用的是Array.apply()方法
val arr2 = Array(1,54,46,15,45)

数组遍历和访问

// 数组访问
println(arr(0))
// 3. 数组的遍历
// 1) 普通for循环 until前闭后开
for (i <- 0 until arr.length){
  println(arr(i))
}
for (i <- arr.indices) println(arr(i))
println("---------------------")
// 2) 直接遍历所有元素,增强for循环
for (elem <- arr2) println(elem)
println("---------------------")
// 3) 迭代器
val iter = arr2.iterator
while (iter.hasNext)
  println(iter.next())
println("---------------------")
// 4) 调用foreach方法
arr2.foreach( (elem: Int) => println(elem) )
arr.foreach( println )
println(arr2.mkString("--")) //打印结果为12--37--42--58--97

数组添加

// 4. 添加元素,在数组后面加用:+
val newArr = arr2.:+(73)
// arr2是不变的
println(arr2.mkString("--"))
println(newArr.mkString("--"))
// 在数组前面添加元素用+:
val newArr2 = newArr.+:(30)
println(newArr2.mkString("--"))
//所有的运算符也是函数,可以省略()
val newArr3 = newArr2 :+ 15
// +一定在数字那边
val newArr4 = 19 +: 29 +: newArr3 :+ 26 :+ 73
println(newArr4.mkString(", "))
7.3.2 可变数组

创建数组

val arr1: ArrayBuffer[Int] = new ArrayBuffer[Int]()
val arr2 = ArrayBuffer(23, 57, 92)
//直接使用println可以输出
println(arr2)

数组遍历和访问

// 遍历和访问同不可变数组
println(arr2(1))

添加元素

// 3. 添加元素
val newArr1 = arr1 :+ 15
// arr1依旧是不变的
println(arr1)
println(newArr1)
println(arr1 == newArr1)
// 对于可变数组而言,非常不推荐将返回的值再赋给另外一个变量=============
val newArr2 = arr1 += 19
// arr1 改变了
println(arr1)
println(newArr2)
// 是一个东西 结果为true
println(arr1 == newArr2)
// 如果newArr2改变了
newArr2 += 13
// arr1 也会改变
println(arr1)
// 在数组前面添加元素
77 +=: arr1
println(arr1)
println(newArr2)
// 在后面添加=======================可变数组推荐使用方法===============
arr1.append(36)
// 在前面添加
arr1.prepend(11, 76)
// 在索引为1的位置添加13和39
arr1.insert(1, 13, 59)
println(arr1)
// 在索引为2的位置,添加一个数组
arr1.insertAll(2, newArr1)
// 在前面添加一个数组
arr1.prependAll(newArr2)
// 在后面添加一个数组
arr1.appendAll(newArr2)

删除元素

// 4. 删除索引3的元素
arr1.remove(3)
println(arr1)
// 从索引0开始删除10个数
arr1.remove(0, 10)
println(arr1)
// 删除第一个值为13的数,如果13不在数组内,啥也不做
arr1 -= 13
7.3.3 可变数组和不可变数组的转换
// 5. 可变数组转换为不可变数组toArray
val arr: ArrayBuffer[Int] = ArrayBuffer(23, 56, 98)
val newArr: Array[Int] = arr.toArray
println(newArr.mkString(", "))
println(arr)
// 6. 不可变数组转换为可变数组toBuffer
val buffer: mutable.Buffer[Int] = newArr.toBuffer
println(buffer)
println(newArr)
7.3.4 多维数组
// 1. 创建二维数组
val array: Array[Array[Int]] = Array.ofDim[Int](2, 3)

// 2. 访问元素
array(0)(2) = 19
array(1)(0) = 25
// 3.遍历二维数组
println(array.mkString(", "))
for (i <- 0 until array.length; j <- 0 until array(i).length){
  println(array(i)(j))
}
for (i <- array.indices; j <- array(i).indices){
  print(array(i)(j) + "\t")
  if (j == array(i).length - 1) println()
}
array.foreach(line => line.foreach(println))
// 简化写法
array.foreach(_.foreach(println))

7.4 列表 相当于JAVA中的LinkedList

7.4.1 不可变列表

创建list

// 1. 创建一个List,不能new,只能使用伴生对象的.apply方法
val list1 = List(23, 65, 87)
println(list1)
// List(73,32)
val list6 = 73 :: 32 :: Nil
// List(17,28,59,6 )
val list7 = 17 :: 28 :: 59 :: 16 :: Nil

访问和遍历list

// 2. 访问和遍历元素
println(list1(1))
//    无法修改元素的值 list1(1) = 12
list1.foreach(println)

添加元素

val list2 = 10 +: list1
val list3 = list1 :+ 23
println(list1)
println(list2)
println(list3)
println("==================")
// 调用特殊的方法::将51放在list的前面
val list4 = list2.::(51)
println(list4)
// 一般用Nil.::(元素)在创建新list
val list5 = Nil.::(13)
println(list5)
// List(73,32)
val list6 = 73 :: 32 :: Nil
// List(17,28,59,16)
val list7 = 17 :: 28 :: 59 :: 16 :: Nil
println(list7)

合并列表

// 4. 合并列表
// List(73,32)
val list6 = 73 :: 32 :: Nil
// List(17,28,59,16)
val list7 = 17 :: 28 :: 59 :: 16 :: Nil
val list8 = list6 :: list7
// list8=List(List(73,32),17,28,59,16)
println(list8)
// list9=List(73,32,17,28,59,16)
val list9 = list6 ::: list7
println(list9)
// 结果同上
val list10 = list6 ++ list7
println(list10)
7.4.2可变列表
// 1. 创建可变列表 
val list1: ListBuffer[Int] = new ListBuffer[Int]()
val list2 = ListBuffer(12, 53, 75)
println(list1)
println(list2)
println("==============")

// 2. 添加元素
list1.append(15, 62)
list2.prepend(20)
// 在索引为1的位置加19,22
list1.insert(1, 19, 22)
println(list1)
println(list2)

println("==============")
31 +=: 96 +=: list1 += 25 += 11
println(list1)
println("==============")
// 3. 合并list
val list3 = list1 ++ list2
// list1 list2并不更改
println(list1)
println(list2)

println("==============")
// list2改变,因为有:是从右到左改变
list1 ++=: list2
println(list1)
println(list2)

println("==============")

// 4. 修改索引为3的元素
list2(3) = 30
// 修改索引为0的元素为89
list2.update(0, 89)
println(list2)

// 5. 删除元素
list2.remove(2)
// 指定删除值为25的值
list2 -= 25
println(list2)

7.5 集合Set

7.5.1 不可变集合
// 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)
7.5.2 可变集合
// 1. 创建set,使用mutable.Set
val set1: mutable.Set[Int] = mutable.Set(13, 23, 53, 12, 13, 23, 78)
println(set1)
println("==================")

// 2. 添加元素
val set2 = set1 + 11
// set1并没有改变
println(set1)
println(set2)
// set1改变了
set1 += 11
println(set1)
// 修改了set就返回true
val flag1 = set1.add(10)
println(flag1)
println(set1)
val flag2 = set1.add(10)
println(flag2)
println(set1)
println("==================")

// 3. 删除元素
set1 -= 11
println(set1)
val flag3 = set1.remove(10)
println(flag3)
println(set1)
val flag4 = set1.remove(10)
println(flag4)
println(set1)
println("==================")

// 4. 合并两个Set,set1改变set2不变
set1 ++= set2

7.6 Map

7.6.1 不可变Map
// 1. 创建map
val map1: Map[String, Int] = Map("a" -> 13, "b" -> 25, "hello" -> 3)
println(map1)
println(map1.getClass) //class scala.collection.immutable.Map$Map3
println("==========================")
// 2. 遍历元素
map1.foreach(println)
map1.foreach( (kv: (String, Int)) => println(kv) )
println("============================")
// 3. 取map中所有的key 或者 value
for (key <- map1.keys){
  println(s"$key ---> ${map1.get(key)}") //输出为a ---> Some(13)
}
// 4. 访问某一个key的value
println("a: " + map1.get("a").get) //获得具体的值map1.get("a").get)
// 获得key为a的值
println(map1("a"))
println("c: " + map1.get("c"))
// 如果c不存在,返回0
println("c: " + map1.getOrElse("c", 0))
7.6.2 可变Map
// 1. 创建map
val map1: mutable.Map[String, Int] = mutable.Map("a" -> 13, "b" -> 25, "hello" -> 3)
println(map1)
println(map1.getClass) // class scala.collection.mutable.HashMap
println("==========================")

// 2. 添加元素
map1.put("c", 5)
map1.put("d", 9)
println(map1)
// 注意是使用(()),原因是若只加一个(),编译器会以为省略了一个(),中间的内容又是另外一个函数,也就是两个函数
map1 += (("e", 7))
println(map1)
println("====================")

// 3. 删除元素
println(map1("c"))
map1.remove("c")
println(map1.getOrElse("c", 0))
map1 -= "d"
println(map1)
println("====================")

// 4. 修改元素
map1.update("c", 5)
map1.update("e", 10)
println(map1)
println("====================")

// 5. 合并两个Map,map2是不可变的
val map2: Map[String, Int] = Map("aaa" -> 11, "b" -> 29, "hello" -> 5)
// map1修改
map1 ++= map2
println(map1)
println(map2)
println("---------------------------")
val map3: Map[String, Int] = map2 ++ map1
println(map1)
println(map2)
println(map3)

7.7 元组

元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。元组中最大只能有 22 个元素。

// 1. 创建元组
val tuple: (String, Int, Char, Boolean) = ("hello", 100, 'a', true)
println(tuple)

// 2. 访问数据,从下标1开始
println(tuple._1)
println(tuple._2)
println(tuple._3)
println(tuple._4)

println(tuple.productElement(1))

println("====================")
// 3. 遍历元组数据
for (elem <- tuple.productIterator)
  println(elem)

// 4. 嵌套元组
val mulTuple = (12, 0.3, "hello", (23, "scala"), 29)
println(mulTuple._4._2)

7.8 集合函数

7.8.1 常用函数
val list = List(1,3,5,7,2,89)
val set = Set(23,34,423,75)

//    (1)获取集合长度,只有线性序列才有的属性
println(list.length)

//    (2)获取集合大小 set不能获取长度
println(set.size)

//    (3)循环遍历
for (elem <- list)
  println(elem)

set.foreach(println)

//    (4)迭代器
for (elem <- list.iterator) println(elem)

println("====================")
//    (5)生成字符串
println(list)
println(set)
println(list.mkString("--"))

//    (6)是否包含
println(list.contains(23))
println(set.contains(23))
7.8.2 衍生操作
val list1 = List(1,3,5,7,2,89)
val list2 = List(3,7,2,45,4,8,19)

//    (1)获取集合的头1
println(list1.head)

//    (2)获取集合的尾(不是头的就是尾)List(3, 5, 7, 2, 89)
println(list1.tail)

//    (3)集合最后一个数据19
println(list2.last)

//    (4)集合初始数据(不包含最后一个)List(3, 7, 2, 45, 4, 8)
println(list2.init)

//    (5)反转List(89, 2, 7, 5, 3, 1)
println(list1.reverse)

//    (6)取前(后)n个元素
println(list1.take(3))
println(list1.takeRight(4))

//    (7)去掉前(后)n个元素
println(list1.drop(3))
println(list1.dropRight(4))

println("=========================")
//    (8)并集
val union = list1.union(list2)
println("union: " + union)
println(list1 ::: list2)

// 如果是set做并集,会去重
val set1 = Set(1,3,5,7,2,89)
val set2 = Set(3,7,2,45,4,8,19)

val union2 = set1.union(set2)
println("union2: " + union2)
println(set1 ++ set2)
println("-----------------------")

//    (9)交集
val intersection = list1.intersect(list2)
println("intersection: " + intersection)
println("-----------------------")

//    (10)差集,存在一个集合中(list1),但不存在另外一个集合中(list2)
val diff1 = list1.diff(list2)
val diff2 = list2.diff(list1)
println("diff1: " + diff1)
println("diff2: " + diff2)
println("-----------------------")

//    (11)拉链(a,b,c) (d,e,f,g) => ((a,d),(b,e),(c,f))
println("zip: " + list1.zip(list2))//List((1,3), (3,7), (5,2), (7,45), (2,4), (89,8))
println("zip: " + list2.zip(list1))//List((3,1), (7,3), (2,5), (45,7), (4,2), (8,89))
println("-----------------------")

//    (12)滑窗,一定划过整个数组
// list1(1,3,5,7,2,89) => (1, 3, 5) (3, 5, 7) (5, 7, 2) (7, 2, 89)
for (elem <- list1.sliding(3))
  println(elem)
println("-----------------------")
// 窗口大小为4,每次滑动2个
for (elem <- list2.sliding(4, 2))
  println(elem)

println("-----------------------")
for (elem <- list2.sliding(3, 3))
  println(elem)
7.8.3 数学操作
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)

//    (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))
println(list2.sorted)

// 5.2 sortBy设置排序的方式
println(list2.sortBy(_._2))
println(list2.sortBy(_._2)(Ordering[Int].reverse))

// 5.3 sortWith
println(list.sortWith( (a: Int, b: Int) => {a < b} ))
// 从小到大
println(list.sortWith( _ < _ ))
println(list.sortWith( _ > _))
7.8.4 map操作
val list = List(0,1,2,3,4,5,6,7,8,9)

// 1. 过滤filter
// 选取偶数
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. 扁平化flatten
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("=======================")

// 4. 扁平映射flatMap
// 将一组字符串进行分词,并保存成单词的列表
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)

val flatmapList = strings.flatMap(_.split(" "))
println(flatmapList)
println("========================")

// 5. 分组groupBy
// 分成奇偶两组Map(1 -> List(1, 3, 5, 7, 9), 0 -> List(0, 2, 4, 6, 8))
val groupMap: Map[Int, List[Int]] = list.groupBy( _ % 2)
// Map(奇数 -> List(1, 3, 5, 7, 9), 偶数 -> List(0, 2, 4, 6, 8))
val groupMap2: Map[String, List[Int]] = list.groupBy( data => if (data % 2 == 0) "偶数" else "奇数")

println(groupMap)
println(groupMap2)

// 给定一组词汇,按照单词的首字母进行分组groupBy
// Map(b -> List(bob), j -> List(japan), a -> List(america, alice), c -> List(china, canada, cary))
val wordList = List("china", "america", "alice", "canada", "cary", "bob", "japan")
println( wordList.groupBy( _.charAt(0) ) )
7.8.5 reduce操作

foldreduce的区别就是fold是具有初始值的,是以初始值为主的计算

// 1. reduce 示例为求和
println(list.reduce( _ + _ ))
// 从左往右算
println(list.reduceLeft(_ + _))
// 从右往左算
println(list.reduceRight(_ + _))
println("===========================")

val list2 = List(3,4,5,8,10)
// 从左往右减
println(list2.reduce(_ - _))    // -24
println(list2.reduceLeft(_ - _))
// 底层代码是递归调用
println(list2.reduceRight(_ - _))    // 3 - (4 - (5 - (8 - 10))), 6

println("===========================")
// 2. fold有初始值
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
7.8.6 map合并
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
    // 以map2作为初始值,底层是递归调用的,所以map2应当为可变Map
    // 因为fold中两个类型必须一样,所以使用foldLeft
    val map3 = map1.foldLeft(map2)(
    // mergedMap表示结果,初始值为map2,kv表示的是map1中的元素
    (mergedMap, kv) => {
        val key = kv._1
        val value = kv._2
        println(kv._1+' '+kv._2)
        mergedMap(key) = mergedMap.getOrElse(key, 0) + value
        mergedMap
      }
7.8.7 单词统计
 val stringList: List[String] = List(
      "hello",
      "hello world",
      "hello scala",
      "hello spark from scala",
      "hello flink from scala"
    )

    // 1. 对字符串进行切分,得到一个打散所有单词的列表
//    val wordList1: List[Array[String]] = stringList.map(_.split(" "))
//    val wordList2: List[String] = wordList1.flatten
//    println(wordList2)
    val wordList:List[String] = stringList.flatMap(_.split(" "))
    println(wordList)

    // 2. 相同的单词进行分组,groupBy传入的函数就是(word返回值为它自己)
    val groupMap: Map[String, List[String]] = wordList.groupBy(word => word)
    println(groupMap)

    // 3. 对分组之后的list取长度,得到每个单词的个数
    val countMap: Map[String, Int] = groupMap.map(kv => (kv._1, kv._2.length))
    println(countMap)
    // 4. 将map转换为list,并排序取前3
    val sortList: List[(String, Int)] = countMap.toList
      .sortWith( _._2 > _._2 )
      .take(3)

    println(sortList)

结果

List(hello, hello, world, hello, scala, hello, spark, from, scala, hello, flink, from, scala)
Map(world -> List(world), flink -> List(flink), spark -> List(spark), scala -> List(scala, scala, scala), from -> List(from, from), hello -> List(hello, hello, hello, hello, hello))
Map(world -> 1, flink -> 1, spark -> 1, scala -> 3, from -> 2, hello -> 5)
List((hello,5), (scala,3), (from,2))

7.8.8 复杂单词统计
// 1. 将字符串打散为单词,并结合对应的个数包装成二元组
val preCountList: List[(String, Int)] = tupleList.flatMap(
  tuple => {
    val strings: Array[String] = tuple._1.split(" ")
    strings.map( word => (word, tuple._2) )
  }
)
println(preCountList)
// 2. 对二元组按照单词进行分组
val preCountMap: Map[String, List[(String, Int)]] = preCountList.groupBy( _._1 )
println(preCountMap)

// 3. 叠加每个单词预统计的个数值
val countMap: Map[String, Int] = preCountMap.mapValues(
  tupleList => tupleList.map(_._2).sum
)
println(countMap)

// 4. 转换成list,排序取前3
val countList = countMap.toList
  .sortWith(_._2 > _._2)
  .take(3)
println(countList)

7.9 队列

// 创建一个可变队列
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)

7.10 并行集合

// 串行执行Vector(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...)
val result: immutable.IndexedSeq[Long] = (1 to 100).map(
  x => Thread.currentThread.getId
)
println(result)
// 并行执行ParVector(12, 12, 12, 12, 12, 12, 18, 18, 16, 16, 16, 19, 19...)
val result2: ParSeq[Long] = (1 to 100).par.map(
  x => Thread.currentThread.getId
)
println(result2)

8 模式匹配

代替了JAVA中的switch case

  1. 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句, 若此时没有 case _ 分支,那么会抛出 MatchError。
  2. 每个 case 中,不需要使用 break 语句,自动中断 case
  3. match case 语句可以匹配任何类型,而不只是字面量。
  4. => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以 使用{}括起来,也可以不括。

8.1 基本语法

// 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("=========================")

8.2 模式守卫

// 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))

8.3 匹配类型

package chapter08

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))

    println("==================================")

    // 2. 匹配类型
    def describeType(x: Any): String = x match {
      case i: Int => "Int " + i
      case s: String => "String " + s
      // JVM中存在泛型擦除,也就是只能识别List,而不能识别其中的类型
      case list: List[String] => "List " + list
      // 可以识别Array及其内的元素类型
      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)))

    // 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)
    }

    println("=========================")

    // 4. 匹配列表
    // 方式一
    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, ...)"
        // list中有一个元素
        case List(a) => "List(a): " + a
        case _ => "something else"
      }
      println(result)
    }

    // 方式二
    val list1 = List(1, 2, 5, 7, 24)
    val list = List(24)

    list1 match {
      //first: 1, second: 2, rest: List(5, 7, 24)
      case first :: second :: rest => println(s"first: $first, second: $second, rest: $rest")
      case _ => println("something else")
    }


    println("===========================")
    // 5. 匹配元组
    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)
    }
  }
}

8.4 应用时匹配

package chapter08

object Test03_MatchTupleExtend {
  def main(args: Array[String]): Unit = {
    // 1. 在变量声明时匹配
    val (x, y) = (10, "hello")
    println(s"x: $x, y: $y")

    val List(first, second, _*) = List(23, 15, 9, 78)
    println(s"first: $first, second: $second")

    val fir :: sec :: rest = List(23, 15 , 9, 78)
    println(s"first: $fir, second: $sec, rest: $rest")

    println("=====================")

    // 2. for推导式中进行模式匹配
    val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))

    // 2.1 原本的遍历方式
    for (elem <- list){
      println(elem._1 + " " + elem._2)
    }

    // 2.2 将List的元素直接定义为元组,对变量赋值
    for ((word, count) <- list ){
      println(word + ": " + count)
    }

    println("-----------------------")
    // 2.3 可以不考虑某个位置的变量,只遍历key或者value
    for ((word, _) <- list)
      println(word)

    println("-----------------------")

    // 2.4 可以指定某个位置的值必须是多少
    for (("a", count) <- list){
      println(count)
    }
  }
}

8.5 匹配对象

package chapter08

object Test04_MatchObject {
  def main(args: Array[String]): Unit = {
    val student = new Student("alice", 19)

    // 使用伴生对象针对对象实例的内容进行匹配
    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方法,用来对对象属性进行拆解 Option[(String, Int)]
  def unapply(student: Student): Option[(String, Int)] = {
    if (student == null){
      None
    } else {
      Some((student.name, student.age))
    }
  }
}

8.6 样例类

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)

8.7 偏函数

偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如 该偏函数的输入类型为 List[Int],而我们需要的是第一个元素是 0 的集合,这就是通过模式 匹配实现的。

image-20230414225150699

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))
  }
}

9 异常

需要注意以下几点:

  1. Scala 没有“checked(编译期)”异常,即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
  2. 所有异常都是 Throwable 的子类型。throw 表达式是有类型的,就是 Nothing
def main(args: Array[String]): Unit = {
  try{
    val n = 10 / 0
  } catch {
    case e: ArithmeticException => {
      println("发生算术异常")
    }
    case e: Exception => {
      println("发生一般异常")
    }
  } finally {
    println("处理结束")
  }
}
def test():Nothing = {
 throw new Exception("不对")
}

10 隐式转换

当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译

10.1 隐式函数和隐式类

def main(args: Array[String]): Unit = {

  val new12 = new MyRichInt(12)
  println(new12.myMax(15))

  // 1. 隐式函数,将int转换成MyRichInt,需要写在调用代码的前面
  implicit def convert(num: Int): MyRichInt = new MyRichInt(num)
  println(12.myMax(15))
  println("============================")

  // 2. 隐式类,必须放在object或者其他类的内部
  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))
  println("============================")
}
// 自定义类
class MyRichInt(val self: Int) {
  // 自定义比较大小的方法
  def myMax(n: Int): Int = if ( n < self ) self else n
  def myMin(n: Int): Int = if ( n < self ) n else self
}

10.2 隐式参数

就是可以将参数的默认值定义在函数外部

// 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()
// 隐式参数会覆盖参数的默认值,所以输出的是hi atguigu
sayHi

// 简便写法implicitly
def hiAge(): Unit = {
  // 指明调用Int的隐式参数
  println("hi, " + implicitly[Int])
}
hiAge()

11 泛型

11.1 协变和逆变

image-20230415103022271

object Test03_Generics {
  def main(args: Array[String]): Unit = {
    // 1. 协变和逆变
    val child: Parent = new Child
    // 协变,Child是Parent的子类,如果不使用协变,就无法定义
    //val childList: MyCollection[Parent] = new MyCollection[Child]
    // 逆变,Child是SubChild的子类,如果不使用逆变,就无法定义
    val childList: MyCollection[SubChild] = new MyCollection[Child]
  }
}

// 定义继承关系
class Parent {}
class Child extends Parent {}
class SubChild extends Child {}

// 定义带泛型的集合类型,使用逆变
class MyCollection[-E] {}

11.2 泛型上下限

// 2. 上下限,传入的泛型A只能是Child及其子类
def test[A <: Child](a: A): Unit = {
  println(a.getClass.getName)
}
// 下面会报错,因为Parent不是Child及其子类
// test[Parent](new Child)
test[SubChild](new SubChild)
test[Child](new SubChild)
// 下面会报错,因为不能把父类(Child)的对象赋给子类(SubChild)
// test[SubChild](new Child)
// 定义继承关系
class Parent {}
class Child extends Parent {}
class SubChild extends Child {}

11.3 上下文限定

相当于内部包含一个隐式参数B[A]

def f[A : B](a: A) = println(a) 
//等同于 def f[A](a:A)(implicit arg:B[A])=println(a)

说明

上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]] 获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误。

def main(args: Array[String]): Unit = {
  implicit val x = 1
  val y = implicitly[Int]
  // 下面会报错, 因为没有隐式的Double参数
  // val z = implicitly[Double]
  println(y)

  // 使用上下文限定,也就等同于下面的代码,相当于有一个隐式的参数,参数类型为Ordering[A]
  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)
  // 3<4 结果为-1
  println(f[Int](3,4))
}

9 异常

需要注意以下几点:

  1. Scala 没有“checked(编译期)”异常,即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
  2. 所有异常都是 Throwable 的子类型。throw 表达式是有类型的,就是 Nothing
def main(args: Array[String]): Unit = {
  try{
    val n = 10 / 0
  } catch {
    case e: ArithmeticException => {
      println("发生算术异常")
    }
    case e: Exception => {
      println("发生一般异常")
    }
  } finally {
    println("处理结束")
  }
}
def test():Nothing = {
 throw new Exception("不对")
}

10 隐式转换

当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译

10.1 隐式函数和隐式类

def main(args: Array[String]): Unit = {

  val new12 = new MyRichInt(12)
  println(new12.myMax(15))

  // 1. 隐式函数,将int转换成MyRichInt,需要写在调用代码的前面
  implicit def convert(num: Int): MyRichInt = new MyRichInt(num)
  println(12.myMax(15))
  println("============================")

  // 2. 隐式类,必须放在object或者其他类的内部
  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))
  println("============================")
}
// 自定义类
class MyRichInt(val self: Int) {
  // 自定义比较大小的方法
  def myMax(n: Int): Int = if ( n < self ) self else n
  def myMin(n: Int): Int = if ( n < self ) n else self
}

10.2 隐式参数

就是可以将参数的默认值定义在函数外部

// 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()
// 隐式参数会覆盖参数的默认值,所以输出的是hi atguigu
sayHi

// 简便写法implicitly
def hiAge(): Unit = {
  // 指明调用Int的隐式参数
  println("hi, " + implicitly[Int])
}
hiAge()

11 泛型

11.1 协变和逆变

[外链图片转存中…(img-9RgQjphE-1696143222645)]

object Test03_Generics {
  def main(args: Array[String]): Unit = {
    // 1. 协变和逆变
    val child: Parent = new Child
    // 协变,Child是Parent的子类,如果不使用协变,就无法定义
    //val childList: MyCollection[Parent] = new MyCollection[Child]
    // 逆变,Child是SubChild的子类,如果不使用逆变,就无法定义
    val childList: MyCollection[SubChild] = new MyCollection[Child]
  }
}

// 定义继承关系
class Parent {}
class Child extends Parent {}
class SubChild extends Child {}

// 定义带泛型的集合类型,使用逆变
class MyCollection[-E] {}

11.2 泛型上下限

// 2. 上下限,传入的泛型A只能是Child及其子类
def test[A <: Child](a: A): Unit = {
  println(a.getClass.getName)
}
// 下面会报错,因为Parent不是Child及其子类
// test[Parent](new Child)
test[SubChild](new SubChild)
test[Child](new SubChild)
// 下面会报错,因为不能把父类(Child)的对象赋给子类(SubChild)
// test[SubChild](new Child)
// 定义继承关系
class Parent {}
class Child extends Parent {}
class SubChild extends Child {}

11.3 上下文限定

相当于内部包含一个隐式参数B[A]

def f[A : B](a: A) = println(a) 
//等同于 def f[A](a:A)(implicit arg:B[A])=println(a)

说明

上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]] 获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误。

def main(args: Array[String]): Unit = {
  implicit val x = 1
  val y = implicitly[Int]
  // 下面会报错, 因为没有隐式的Double参数
  // val z = implicitly[Double]
  println(y)

  // 使用上下文限定,也就等同于下面的代码,相当于有一个隐式的参数,参数类型为Ordering[A]
  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)
  // 3<4 结果为-1
  println(f[Int](3,4))
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1056977.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenGLES:绘制一个颜色渐变、旋转的3D立方体

一.概述 之前关于OpenGLES实战开发的博文&#xff0c;不论是实现相机滤镜还是绘制图形&#xff0c;都是在2D纬度 这篇博文开始&#xff0c;将会使用OpenGLES进入3D世界 本篇博文会实现一个颜色渐变、旋转的3D立方体 动态3D图形的绘制&#xff0c;需要具备一些基础的线性代数…

mysql面试题8:MySQL的日志有哪些?MySQL的bin log、redo log、undo log区别和联系

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:MySQL的日志有哪些? MySQL的日志有以下几种,以及查看这些日志的命令和操作步骤如下: 错误日志(Error Log): 查看错误日志的命令:SHOW VARI…

2023.10.01-winxpsp3绿色安装jdk1.8

参考: 如何在XP系统配置java8(jdk8)环境 - 简书 jdk-8u381-windows-i586.exe jdk-8u202-windows-i586.exe 实验了一下,8u381不支持winxp xp3 下载地址: https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.htmlhttps://download.oracle.com/otn/ja…

基于Java的电影院购票系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

Strategy

Strategy 动机 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多样&#xff0c;经常改变&#xff0c;如果将这些算法都编码到对象中&#xff0c;将会使对象变得异常复杂&#xff1b; 而且有时候支持不使用的算法也是一个性能负担。如何在运行时根据需要透明地更改对…

buuctf-[WUSTCTF2020]颜值成绩查询

打开环境&#xff0c;随便输个1看看 输个2 发现功能就是输入一个学号&#xff0c;然后返回对应的成绩&#xff0c;就是一个简单的查询操作。 当输入的学号不存在时&#xff0c;只会返回“student number not exists.”。 猜测是盲注题&#xff0c;因为看不见其他的回显信息&a…

计算机毕业设计 基于SSM的民宿推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【iptables 实战】05 iptables设置网络转发实验

一、网络架构 实验效果&#xff0c;通过机器B的转发功能&#xff0c;将机器A的报文转发到机器C 本实验准备三台机器分别配置如下网络 机器A ip:192.168.56.104 机器C ip:10.1.0.10 机器B 两张网卡&#xff0c;分别的ip是192.168.56.106和10.1.0.11 如图所示 如下图所示 二、…

软件安全需求分析

一、实验目的 熟悉软件安全需求分析方法&#xff0c;掌握软件安全分析技术。 二、实验软硬件要求 1、操作系统&#xff1a;windows 7/8/10等 三、实验预习 《软件安全技术》教材第6章 四、实验内容&#xff08;实验步骤、测试数据等&#xff09; 1. 目标&#xff1a;完成一…

10个与AI相关的技术领域

**10个与AI相关的技术领域** 除了与各个科学领域相关的具体挑战之外&#xff0c;AI在科学领域还存在一些共同的技术挑战。特别是&#xff0c;我们确定了以下四个共同的技术挑战&#xff1a;超出分布的泛化、可解释性、由自监督学习提供支持的基础模型和不确定性量化。尽管这些…

SpringBoot Validation入参校验国际化

在 Spring Boot 中&#xff0c;可以使用 Validation 和国际化来实现对入参的校验。 常用的校验 NotNull验证字段值不能为 nullNotEmpty验证字段值不能为 null 或空字符串NotBlank验证字符串字段值不能为空、null&#xff0c;并且必须至少包含一个非空白字符Size验证字符串、…

【PickerView案例12-info_plist-PCH文件介绍 Objective-C语言】

一、给大家介绍一下我们项目的一些文件: 1.这个呢,是项目的基础文件: 一些类啊: 一些图片啊: 还有加载图片, 最主要,就是这个东西:info.plist:文件 info.plist: 2.那,需要大家了解一点,关于它的历史啊: 我们现在用的时候,都是从xcode6.4开始的, 或者说,直…

【Shiro】SpringBoot集成Shiro权限认证《下》

本章节是在上一节的基础上继续完成&#xff0c;如有不明白&#xff0c;请看上一篇文章【Shiro】SpringBoot集成Shiro权限认证《上》。 SQL语句 这里我们需要先准备好SQL语句,如下所示&#xff1a; /* Navicat MySQL Data TransferSource Server : local Source Serv…

美丽的图论

**美丽的图论 ** Prf &#x1f609; 对于 n 个顶点上的树的数量 n^(n-2)&#xff0c;这是凯莱公式&#xff0c;用于计算 n 个顶点上的树的数量&#xff0c;被放置在一个由 4 个标记顶点组成的圆圈中。 使用 Figma 制作 在图论中&#xff0c;树只是一个没有环的图。 树在离散…

Python机器学习-灵敏度分析

文章目录 灵敏度分析详细步骤单参数分析 灵敏度分析详细步骤 灵敏度分析是一种用于确定输入参数变化对模型输出结果的影响程度的方法。以下是进行灵敏度分析的一般步骤&#xff1a; 确定模型&#xff1a;选择需要进行灵敏度分析的模型&#xff0c;该模型必须具有可变参数和可…

算法框架-LLM-1-Prompt设计(一)

原文&#xff1a;算法框架-LLM-1-Prompt设计&#xff08;一&#xff09; - 知乎 目录 收起 1 prompt-engineering-for-developers 1.1 Prompt Engineering 1.1.1 提示原则 1. openai的环境 2. 两个基本原则 3. 示例 eg.1 eg.2 结构化输出 eg.3 模型检验 eg.4 提供示…

OpenCV实现视频的读取、显示、保存

目录 1&#xff0c;从文件中读取视频并播放 1.2代码实现 1.3效果展示 2&#xff0c;保存视频 2.1 代码实现 2.2 结果展示 1&#xff0c;从文件中读取视频并播放 在OpenCV中我们需要获取一个视频&#xff0c;需要创建一个VideoCapture对象,指定你要读取的视频文件&am…

八大排序(三)堆排序,计数排序,归并排序

一、堆排序 什么是堆排序&#xff1a;堆排序&#xff08;Heap Sort&#xff09;就是对直接选择排序的一种改进。此话怎讲呢&#xff1f;直接选择排序在待排序的n个数中进行n-1次比较选出最大或者最小的&#xff0c;但是在选出最大或者最小的数后&#xff0c;并没有对原来的序列…

聊聊并发编程——线程池

目录 Java线程池 处理流程 线程池主要参数 常见的拒绝策略 execute和submit区别 关闭线程池 常见的线程池 newSingleThreadExecutor newFixedThreadPool newCachedThreadPool newScheduledThreadPool 线程池的状态 Java线程池 运用场景最多的并发框架&#xff0c;…

【面试总结大纲】

面试 springSpring AOP的具体实现核心概念分别指的是什么?基于注解的切面实现主要包括以下几个步骤&#xff1a;两个切面&#xff0c;它们之间的顺序是怎么控制的 springmvc的工作流程设计模式原则Spring 框架中用到了哪些设计模式&#xff1f; spring Spring AOP的具体实现 …