# Scala-Study **Repository Path**: yuyudeqiu/scala-study ## Basic Information - **Project Name**: Scala-Study - **Description**: Scala学习代码,以及Spark - **Primary Language**: Scala - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-10-26 - **Last Updated**: 2022-06-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Scala ## 1. HelloWorld ```scala object HelloWorld { def main(args: Array[String]): Unit = { System.out.println("Hello Java") println("Hello Scala") } } ``` ## 2. 数据类型 ### 2.1 变量的语法声明 ```scala // var | val 变量名 :变量类型 = 变量值 var username : String = "CJ" val userpswd = "1234" // 变量的类型如果能够通过变量值推断出来,那么可以省略类型声明。Scala编译器在编译时自动声明编译的 // var 可变变量 var username: String = "ccc" username = "qqq" // val 不可变变量 val password: String = "1234" // password = "4321" // ERROR // 变量初始化 Scala语法中必须显示进行初始化操作 // var q // ERROR val q = "qqq" ``` ### 2.2 Scala数据类型 Scala是完全面向对象的语言,所以不存在基本数据类型的概念,有的只是**任意值对象类型(AnyVal)**和**任意引用对象类型(AnyRef)** ![image-20220328171340719](https://gitee.com/yuyudeqiu/mdpicture-resitory1/raw/master/img/image-20220328171340719.png) ```scala // 任意对象数据类型(AnyVal) val byte: Byte = 12 val short: Short = 23 val int: Int = 45 val long: Long = 56 val float: Float = 6.78F val double: Double = 78.90 val char: Char = 'C' val string: String = "CJ" val boolean: Boolean = true val unit: Unit = test() println(unit) // () // Unit表示无值,和其他语言中的void等同。用作不返回任何结果的结果类型。Unit只有一个实例,写作() ``` ### 2.3 类型转换 ```scala // 自动类型转换(隐式转换) val b: Byte = 10 val s: Short = b val i: Int = s val lon: Long = i ``` ```scala // val c:Char = 'A' + 1 // 这里虽然IDEA提示是ERROR,但是可以运行的 // println(c) // B // 提示错的原因是因为 1 默认是一个Int,然后char是比Int精度更小的 ``` ```scala // 强制类型转换 var a: Int = 10 var bb: Byte = a.toByte // 基本上Scala的AnyVal类型之间都提供了相应转换的方法 // 字符串类型转化 // scala是完全面向对象的语言,所有的类型都提供了toString方法,可以直接转换为字符串 val i: Int = 10 val s: String = i.toString // 任意类型都提供了和字符串进行拼接的方法 val ss: String = "cj" + 1 ``` ## 3. 运算符 scala运算符的使用和Java运算符的使用基本相同,只有个别细节上不同 ```scala // Scala中 String中的 == 与Java的不同 val s1 = new String("CJ") val s2 = new String("CJ") println(s1 == s2) // true println(s1.equals(s2)) // true println(s1.eq(s2)) // false // 不同的是 s1 == s2 的结果是true 这是Scala中做的一个优化 把 == 优化为是比较String中的内容 // == 使用更优于 equals 因为在有String对象为空的时候,并不会出现Exception // 当然也保留了地址比较 即使用 eq ``` ```scala // a++ // ERROR // a-- // ERROR // ++运算有歧义,容易理解出现错误,所以scala中没有这样的语法,所以采用 +=的方式来代替 ``` **运算符的本质:**在Scala中其实是没有运算符的,所有运算符都是方法。 ```scala val i: Int = 10 val j: Int = i.+(10) // scala是完全面向对象的语言,所以数字其实也是对象 val k: Int = j + (20) // 当调用对象的方法时,点.可以省略 val m: Int = k + 30 // 如果函数参数只有一个,或者没有参数,()可以省略 println(m) // 70 ``` ## 4. 流程控制 ### 4.1 if ```scala if (age < 18) { println("未成年") } else if (age < 30) { println("青年") } else { println("...") } ``` ```scala // Scala中的表达式都是有返回值的 val age = 60 val result = if (age < 18) { "儿童" } else if (age <= 30) { "青年" } else if (age <= 50) { "中年" } else { "老年" } println(result) // 老年 ``` **Scala语言中没有三元运算符的,使用if分支判断来代替三元运算符** ### 4.2 for #### 4.2.1 to&until&Range ```scala for (i: Int <- 1 to 5) { // 1 <= i <= 5 print(i + " ") } for (i: Int <- 1 until 5) { // 1 <= i < 5 print(i + " ") } for (i <- Range(1, 5)) { // 1 <= i < 5 print(i + " ") } // 循环步长 for (i <- 1 to 5 by 2) { print(i + " ") } ``` #### 4.2.2 循环守卫 **循环时可以增加条件来决定是否继续循环体的执行,这里的判断条件我们称之为循环守卫** ```scala for (i <- Range(1, 5) if i != 3) { println("====") println(s"i = ${i}") } ``` 循环守卫与在for中使用if的区别: ```scala for (i <- Range(1, 5)) { println("####") if (i != 3) { println(s"i = ${i}") } } ``` 循环守卫的执行结果: ![image-20220328173347055](https://gitee.com/yuyudeqiu/mdpicture-resitory1/raw/master/img/image-20220328173347055.png) for中使用if的执行结果: ![image-20220328173627308](https://gitee.com/yuyudeqiu/mdpicture-resitory1/raw/master/img/image-20220328173627308.png) for中使用if要比循环守卫多了一次输出,守卫循环如果条件不成立是不会进入循环体的,直接走下一次循环。 #### 4.2.3 嵌套循环 嵌套循环除了常规的方式,Scala还提供了另一种方式 **常规方式**: ```scala for (i <- Range(1, 3)) { for (j <- Range(1, 3)) { println(s"i=${i}, j=${j}") } } ``` **Scala方式**: ```scala for (i <- Range(1, 3); j <- Range(1, 3)) { println(s"i=${i}, j=${j}") } ``` **两种方式的区别**: ```scala for (i <- Range(1, 3); j <- Range(1, 1)) { // 其中有一个不满足都不会进入循环 println(s"i=${i}, j=${j}") } ``` ```scala for (i <- Range(1, 3)) { println("===") // for (j <- Range(1, 1)) { println(s"i=${i}, j=${j}") } } ``` 内层循环在第一步时就已经不满足条件了,前者没有输出任何东西;而后者还可以在内层循环中间执行其他操作,如上面代码中的打印`===`。也就是说,前者中有任何一个条件不满足都不会进入循环,而后者,只要满足即进入。 #### 4.2.4 循环返回值 ```scala // 这里的返回值并不一定都是有值的 val result1 = for (i <- Range(1, 5)) { i } println(result1) // () 即Unit ``` 如果希望for循环表达式的返回值有具体的值,需要使用关键字**yield** ```scala val result2 = for (i <- Range(1, 5)) yield { i } println(result2) // Vector(1, 2, 3, 4) ``` ### 4.3 while ```scala var i = 0 while (i < 5) { println(s"i=${i}") i += 1 } // do.. while var j = 5 do { println(s"j=${j}") j += 1 } while (i < 5) ``` ### 4.4 循环中断 scala是完全面向对象的语言,所以**无法使用break,continue关键字**这样的方式来中断,或继续循环逻辑,而是**采用了函数式编程的方式代替**了循环语法中的break和continue **用 if 代替 continue:** ```scala for (i <- Range(1, 5)) { if (i != 3) { println(s"i=${i}") } // 相当于 // if (i == 3) continue // println(s"i=${i}") } ``` **break使用一个Breaks对象来实现**,原理是抽象控制,实际上就是满足条件时抛出异常并处理 ```scala scala.util.control.Breaks.breakable { for (i <- 1 until 5) { if (i == 3) { scala.util.control.Breaks.break } println(s"i=${i}") } } ``` ## 5. 函数式编程(重点) 函数式编程就是:将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的功能按照指定的步骤,解决问题 **函数与方法的区别:** - scala 方法是类的一部分,而函数是一个对象,可以赋值给一个变量。换句话来说在**类中定义的函数即是方法** - **函数没有重载和重写的概念**,但是**函数可以嵌套声明使用**,方法就没有这个能力了 - 方法和函数同名情况下**调用的是函数** - 实际上**函数编译之后也是一个新的方法**,只是用private static final修饰的 ### 5.1 函数定义 ```scala [修饰符] def 函数名 ( 参数列表 ) [:返回值类型] = { 函数体 } ``` **无参无返回值:** ```scala def fun1(): Unit = { println("CJ") } // 调用 fun1() // CJ // 如果函数声明时,没有参数,那么调用时,可以省略小括号 fun1 // CJ ``` **无参有返回值** ```scala def fun2(): String = { return "JC" } // 调用 val str1 = fun2() val str2 = fun2 println(str1) // JC println(str2) // JC ``` **有参无返回值** ```scala def fun3(name: String): Unit = { println(name) } // 调用 fun3("CB") // CB // 不同于方法,函数的参数只有一个,也不能省略小括号 // fun3 "CB" // ERROR ``` **有参有返回值** ```scala def fun4(name: String): String = { return s"name: ${name}" } // 调用 val str3 = fun4("CCC") println(str3) // name: CCC ``` **多参无返回值** ```scala def fun5(name: String, age: Int): Unit = { println(s"name: ${name}, age: ${age}") } // 调用 fun5("JJJ", 24) // name: JJJ, age: 24 ``` **多参有返回值** ```scala def fun6(name: String, age: Int): String = { return s"name: ${name}, age: ${age}" } // 调用 val str4 = fun6("QQQ", 23) println(str4) // name: QQQ, age: 23 ``` ### 5.2 函数参数 **可变参数:** ```scala // 可变参数不能放置在参数列表的前面,一般放置在参数列表的最后 def fun1(num: Int, names: String*): Unit = { println(s"num: $num") println(names) } // 调用 fun1(5, "cc", "dd", "ff", "aa", "zz") ``` **参数默认值:** ```scala def fun2(name: String, password: String = "123456"): Unit = { println(s"name: ${name}, password: ${password}") } // 调用 fun2("CCC") // name: CCC, password: 123456 fun2("CJ", "321") // name: CJ, password: 321 ``` **带名参数:** ```scala def fun3(name: String = "QQ", age: Int): Unit = { println(s"name: ${name}, age: ${age}") } // 调用 // 如果带默认值的参数在前面,而又不想给他传值,那就可以用上带名参数 fun3("CC", 24) // name: CC, age: 24 fun3(age = 23) // name: QQ, age: 23 ``` ### 5.3 函数至简原则 实际上就是通过编译器的动态判定功能,将函数声明中能简化的地方全部都进行了简化,简单来说就是:能省则省 1. return关键字可以省略 ```scala // // 函数会将满足条件的最后一行代码的执行结果作为函数的返回值 def test1(): String = { "cj" } // 调用 println(test1()) // cj ``` 2. 如果函数返回数据,那么可以推断出返回值类型的话,返回值类型可以省略 ```scala def test2() = { "jc" } // 调用 println(test2()) // cj ``` 3. 如果函数体的代码只有一行,大括号可以省略 ```scala def test3() = "CJ" // 调用 println(test3()) ``` 4. 如果函数的参数列表中没有声明任何的参数,那么参数列表可以省略 ```scala def test4 = "JC" // println(test4()) // ERROR // 当省略参数列表的声明时,调用这个函数不能增加小括号 println(test4) // JC ``` 5. 如果函数声明为:Unit,那么函数体中的return关键字不会返回 ```scala def test5(): Unit = { return "CJJ" } // 调用 println(test5()) // () // def test5_2() ={ // ERROR // return "CCJ" // } // 如果函数体中使用return返回结果,那么一定要声明返回值类型 def test5_2(): String = { return "CCJ" } ``` 6. 关键字def和函数名也可以省略 ```scala // 即 匿名函数 // 省略的同时,也要将返回值类型同时省略,将等号增加一个箭头 // 匿名函数不能独立使用 val f: () => String = () => { "CJ" } println(f()) // CJ ``` ### 5.4 函数作为对象 ```scala def fun1(): Unit = { println("fun1...") } // 这里实际上是把函数调用之后的返回值给f1 f2 val f1 = fun1 val f2 = fun1() println(f1) // () println(f2) // () ``` 如果**将函数作为整体**,而不是执行结果赋值给变量,那么需要采用特殊符号: `_` (下划线) ```scala val f3 = fun1 _ println("*********") f3() ``` 函数在独立使用的时候,参数声明没有个数限制,但是如果将函数作为对象给别人使用,那么函数的参数声明最多为22个 ### 5.5 函数作为参数 ```scala def test(f: () => Unit): Unit = { f() } // 函数 def fun1(): Unit = { println("CJ") } // 调用 test(fun1 _) // CJ ``` **有参函数作为参数:** ```scala def test2(f: (Int, Int) => Int): Unit = { println(f(10, 20)) } // 有参函数 def fun2(x: Int, y: Int): Int = { x + y } // 调用 test2(fun2) // 30 ``` 上面定义函数,实际上是可以**用匿名函数来代替**,并不断简化: 1. 如果函数体只有一行代码,大括号可以省略 ```scala test2( (x: Int, y: Int) => x + y ) ``` 2. 如果参数的类型可以推断出来,那么参数类型可以省略 ```scala test2( (x, y) => x + y ) ``` 3. 如果参数只有一个的话,参数列表可以省略 这里是两个就省略不了了 4. 如果参数在使用时,按照顺序只使用了一次,那么可以使用下划线代替参数 ```scala test2(_ + _) ``` ### 5.6 函数作为返回值 ```scala def test(): Unit = { println("function...") } def fun(): () => Unit = { test _ } val f = fun _ val ff = f() // 调用内部函数 ff() // function... f()() //function... // 将函数作为返回值,一般应用于返回内部的函数在外部使用 ``` ### 5.7 闭包 一个函数使用了外部变量,把这个变量包含到了它的内部来使用,改变了这个变量的声明周期,将当前的代码形成了一个闭合的环境,这个环境称之为闭包环境,简称闭包 ```scala def outer(a: Int) = { def inner(b: Int): Int = { a + b } inner _ } ``` **内部函数在外部使用的时候会有闭包** ```scala println(outer(10)(20)) // 30 ``` **将函数作为对象使用时会有闭包** ```scala val f = outer(10) val ff = f(20) println(ff) // 30 ``` **所有匿名函数都有闭包** Scala2.12版本之前闭包功能采用的是匿名函数类型实现;Scala2.12版本闭包功能采用的是更改函数声明实现。通过编译之后产生的字节码可以得知 ### 5.8 抽象控制 ```scala def fun(op: => Unit): Unit = { op } // 类型不完整,那么在传递的时候,也不完整;只有传递代码就可以,不需要完整的声明 // 可以采用控制抽象设计语法 fun { println("CJ") } ``` ### 5.9 函数柯里化 指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。**用于将无关的参数分离开** ```scala def test(a: Int)(b: Int): Unit = { for (i <- 1 to a) { println(i) } for (j <- 1 to b) { println(j) } } val a = 10 val b = 15 test(a)(b) ``` ### 5.10 惰性函数 当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行 ```scala def fun(): String = { println("function...") "cj" } lazy val a = fun() println("-----------") println(a) ``` 执行效果如下: ![image-20220328181837568](https://gitee.com/yuyudeqiu/mdpicture-resitory1/raw/master/img/image-20220328181837568.png) ### 5.11 递归 ```scala def myRecursion(num: Int): Int = { if (num < 1) { return 1 } num * myRecursion(num - 1) } val i = myRecursion(5) println(i) // 120 ``` **尾递归(伪递归):**scala中尾递归实际上不是真正的递归,是由编译器进行优化,形成了while循环。所以应该说是**伪递归** ```scala def test(): Unit = { println("cj") test() } test() ``` 上面代码中并不会出现**StackOverflowError**,因为编译器在编译过程中已经优化成了while循环: ![image-20220328182118367](https://gitee.com/yuyudeqiu/mdpicture-resitory1/raw/master/img/image-20220328182118367.png) 如果是普通递归,那就会出现StackOverflowError ```scala def test1(): Unit = { test1() println("cj") } ``` ## 6. 面向对象(重点) ### 6.1 包 Scala对package进行扩展 ```scala package com package ccc package scala { class Test { def test(): Unit = { println("test...") } } package base6_object { object Object1_package { def main(args: Array[String]): Unit = { /* Scala package: 1. 可以在源码中使用多次package关键字 2. 源码的路径与包路径没有直接关系 3. 明确包的作用域,可以在package后面增加大括号 4. 同一个源码中,子包可以直接访问父包中的内容 5. Scala可以将包当成对象来用 */ println("package...") val test = new Test() test.test() } } } } ``` **import的使用:** 1. 星号 * 在Scala中有特殊用途,不能使用在import语法中,需要采用特殊符号:下划线 `_` ```scala import java.util._ ``` 2. import 关键字可以在任何地方使用 ```scala import java.util.Date new Date() ``` 3. 可以在一行中导入同一个包中的多个类 ```scala import java.util.{ArrayList, List, LinkedList} ``` 4. 导包 ```scala import java.util new util.ArrayList() ``` 5. 隐藏类 ```scala import java.util._ import java.sql.{Date => _, _} // 隐藏类 new Date() // Date导的是util包下的, 因为上面被隐藏了 ``` 6. Scala中导入类的操作,是以相对路径(当前包路径)方式导入的 ```scala // 如果想要使用绝对路径,需要使用特殊操作: _root_ println(new java.util.HashMap()) // com.ccc.scala.base6_object.java.util.HashMap@71c7db30 println(new _root_.java.util.HashMap()) // {} // 在另外地方定义的HashMap package java { package util { class HashMap { } } } ``` 7. 可以给类起别名 ```scala import _root_.java.util.{HashMap => javaHashMap} println(new javaHashMap()) ``` ### 6.2 类 使用class关键字声明类,通过new的方式构建类的对象 ```scala def main(args: Array[String]): Unit = { val test = new Test() } class Test { } ``` ### 6.3 属性 属性就是类中的变量,在编译时,编译器会将变量编译为类的(私有的)属性,同时提供了相对于属性的set和get方法 (不叫get和set),给类的属性赋值,等同于调用他的set方法。同理 get方法也是。 ```scala def main(args: Array[String]): Unit = { val test = new Test() test.name = "JC" println(test.name) // val声明的属性,在编译时会添加final关键字,编译器不会提供属性的set方法 // test.age = 23 // ERROR // Scala中给属性提供的set,get方法不遵循bean规范 // 为了 可以使用注解 @BeanProperty 在Scala的类中给属性添加get set 方法 test.setEmail("1234") println(test.getEmail) } class Test { var name: String = "CJ" val age: Int = 24 // Scala中变量必须显式的初始化 // var email: String // ERROR // 如果希望类的属性和Java一样可以和Java一样由系统初始化,手动赋值,可以使用特殊符号:下划线 _ @BeanProperty var email: String = _ } ``` ![image-20220328183515279](https://gitee.com/yuyudeqiu/mdpicture-resitory1/raw/master/img/image-20220328183515279.png) ### 6.4 访问权限 ``` private : 私有访问权限,本类 private[包名]: 包访问权限,同包,包私有 protected : 受保护权限,同类 子类 没有同包 : 公共访问权限 ``` ### 6.5 方法 实际上就是类中声明的函数,声明方式完全一样,但是必须通过使用对象进行调用 ```scala def main(args: Array[String]): Unit = { val user = new User() println(user.login("CJ", "123")) println(user.login()) } class User { def login(name: String, password: String): Boolean = { name == "CJ" && password == "123" } def login(): Boolean = { false } } ``` ### 6.6 构造方法 1. 提供无参构造,公共的构造方法 2. scala中构造方法的名称和类名是不一样的,scala是一个完全面向对象的语言,也是完全面向函数的语言,所以类也是一个函数,声明一个类就等同于声明一个函数,类名的后面可以声明小括号,表示构造参数列表 3. 构造方法可以分为2大类:**主构造函数**和**辅助构造函数**。主构造方法:用于完成类的初始化操作;辅助构造方法:方法的名字的this关键字,其他和普通方法一样,作用是在类初始化完成后,做一些辅助功能 ```scala def main(args: Array[String]): Unit = { val user = new User() // user... val user2 = new User("CJ", "123") } class User() { // 构造方法体 & 类的主体内容 println("user...") var username: String = "CJ" def this(name: String) { // 辅助构造函数,使用this关键字声明 this() username = name } def this(name: String, password: String) { // 构造器调用其他另外的构造器,要求被调用构造器必须提前声明 this(name) } def test(): Unit = { println(s"name: ${username}") } } ``` ### 6.7 单例 **单例对象**,就是在程序运行过程中,指定类的对象只能创建一个,而不能创建多个。这样的对象可以由特殊的设计方式获得,也可以由语言本身设计得到,比如object伴生对象 若单例对象名与类名一致,则称该单例对象这个类的**伴生对象**,这个类的所有“静态”内容都可以放置在它的伴生对象中声明 如果类名和伴生对象名称保持一致,那么这个类称之为**伴生类**。 ```scala def main(args: Array[String]): Unit = { // 单例对象 val user1 = new User() // 通过构造器伴生类对象 val user2 = User.apply() // 通过伴生对象的apply方法构造伴生类对象 val user3 = User() // scala编译器省略apply方法,自动完成调用 } class User() { println("构造器初始化对象...") } object User { def apply(): User = { println("apply方法...") new User() } } ``` ### 6.8 抽象 ```scala def main(args: Array[String]): Unit = { // 抽象类无法实例化 // new User() // ERROR new Child() } abstract class User { // Scala中不完整的方法就是抽象,所以无需增加abstract关键字 def test(): Unit // Scala中属性也可以是抽象的 // 编译时,不会在类中声明属性,而是会声明属性的“抽象”的set和get方法, var name: String // 抽象类中可以有完整方法 def test2(): Unit = { println("parent method...") } val age: Int = 24 } class Child extends User { // 子类继承抽象类之后,可以声明为抽象类,也可以将父类的抽象方法补充完整 override def test(): Unit = { } // 属性:编译时,会在类中声明私有属性,同时提供set和get方法,并且为公共的 var name: String = "CJ" // 子类重写父类的完整方法时,必须添加override关键字 override def test2(): Unit = { println("child method...") } // 子类也可以重写父类的完整属性,必须添加override关键字 override val age: Int = 23 } ``` ### 6.9 特征 Scala将多个类的相同特征从类中剥离出来,形成一个独立的语法结构,称之为“特质”(特征)。这种方式在Java中称之为接口,但是Scala中没有接口的概念。所以scala中没有interface关键字,而是采用特殊的关键字**trait**来声明特质, ```scala def main(args: Array[String]): Unit = { val cat = new Cat() cat.eat() cat.run() new Person() with Eat { // 动态混入 override def eat(): Unit = { println("Person eat...") } } } trait Eat { def eat(): Unit } trait Runnable { def run(): Unit } class Person extends Runnable { override def run(): Unit = { println("Person run...") } } // 一个类只有一个特征时,用 extends 关键字进行混入 class Dog extends Runnable { override def run(): Unit = { println("Dog run...") } } // 一个类有多个特征,第一个用 extends ,后续采用with class Cat extends Runnable with Eat { override def run(): Unit = { println("Cat run...") } override def eat(): Unit = { println("Cat eat...") } } // 如果类存在父类的场合,并同时具备某个特征, // 需要使用extends关键字继承父类,使用with关键字混入特征 class User extends Person with Runnable with Eat { override def eat(): Unit = { // ... } override def run(): Unit = { // ... } } ``` ### 6.10 其他 **类型检查和转换** ```scala def main(args: Array[String]): Unit = { // 类型检查和转换 val person = new Person() // 判断对象是否为某个类型的实例 val bool = person.isInstanceOf[Person] if (bool) { val p1: Person = person.asInstanceOf[Person] println(p1) } } class Person { } ``` **枚举类** ```scala def main(args: Array[String]): Unit = { // 枚举类 println(Color.RED) println(Color.YELLOW) println(Color.BLUE) } object Color extends Enumeration { val RED = Value(1, "red") val YELLOW = Value(2, "yellow") val BLUE = Value(3, "blue") } ``` **应用类** ```scala object Object12_App extends App { // 应用类 println("CJ") // 可以直接执行 } ``` **类型别名** ```scala object Object13_Type extends App { type S = String var v: S = "CJ" println(v) // CJ } ``` ## 7. 集合(重点) Scala的集合有三大类:**序列Seq、集Set、映射Map**,所有的集合都扩展自Iterable特质。对于几乎所有的集合类,Scala都同时提供了**可变和不可变的版本**。 ![image-20220328185129281](https://gitee.com/yuyudeqiu/mdpicture-resitory1/raw/master/img/image-20220328185129281.png) ![image-20220328185136981](https://gitee.com/yuyudeqiu/mdpicture-resitory1/raw/master/img/image-20220328185136981.png) ### 7.1 数组 严格意义上,数组不是集合,Scala中给数组一个特定的类型:Array #### 7.1.1 不可变数组 ```scala // 使用集合的伴生类对象构建集合,并同时初始化 // val array = Array.apply(1, 2, 3, 4, 5) val array = Array(1, 2, 3, 4, 5) val array2 = Array(6, 7, 8, 9) // 访问 // Scala只要是以运算符冒号(:)结尾的,那么运算规则为从后往前计算 // val ints1 = array.:+(6) // val ints2 = array.+:(0) val ints1 = array :+ 6 val ints2 = 0 +: array println(ints1.mkString(",")) // 1,2,3,4,5,6 println(ints2.mkString(",")) // 0,1,2,3,4,5 // val ints3 = array.++(array2) val ints3 = array ++ array2 val ints4 = array ++: array2 println(ints3.mkString(",")) // 1,2,3,4,5,6,7,8,9 println(ints4.mkString(",")) // 1,2,3,4,5,6,7,8,9 // foreach是一个循环方法,需要传递一个参数,参数类型就是函数类型 // def foreachFunction(num: Int): Unit = { // println(num) // } // array.foreach(foreachFunction) // 简化 // array.foreach((num: Int) => { // println(num) // }) // array.foreach(num => println(num)) array.foreach(println(_)) ``` ```scala // 多维数组 val matrix = Array.ofDim[Int](3, 3) matrix.foreach(list => println(list.mkString(", "))) /* 0, 0, 0 0, 0, 0 0, 0, 0 */ val arr1 = Array(1, 2, 3, 4) val arr2 = Array(5, 6, 7, 8) // 合并数组 val arr3 = Array.concat(arr1, arr2) println(arr3.mkString(", ")) // 1, 2, 3, 4, 5, 6, 7, 8 // 创建指定范围的数组 val arr4 = Array.range(0, 8) println(arr4.mkString(", ")) // 0, 1, 2, 3, 4, 5, 6, 7 // 创建并填充指定数量的数组 val arr5 = Array.fill[Int](5)(-1) println(arr5.mkString(", ")) // -1, -1, -1, -1, -1 ``` #### 7.1.2 可变数组 ```scala val buffer = new ArrayBuffer[String] // 操作 buffer.append("cj", "CJ", "Carl", "Johnson") buffer.appendAll(Array("C", "CC", "CCC")) println(buffer) // ArrayBuffer(cj, CJ, Carl, Johnson, C, CC, CCC) buffer.insert(1, "QQ") println(buffer) // ArrayBuffer(cj, QQ, CJ, Carl, Johnson, C, CC, CCC) // buffer.update(0, "JC") buffer(0) = "JC" println(buffer) // ArrayBuffer(JC, QQ, CJ, Carl, Johnson, C, CC, CCC) buffer.remove(3, 2) println(buffer) // ArrayBuffer(JC, QQ, CJ, C, CC, CCC) val buffer1 = buffer - "CJ" println(buffer1) // ArrayBuffer(JC, QQ, C, CC, CCC) ``` #### 7.1.3 数组常用方法 ```scala // 常用方法 val array = ArrayBuffer(1, 2, 3, 2, 1, 4, 5) println(array.size) // 7 println(array.length) // 7 println(array.isEmpty) // false println(array.contains(3)) // true println(array.distinct.mkString(", ")) // 1, 2, 3, 4, 5 println(array.reverse.mkString(", ")) // 5, 4, 1, 2, 3, 2, 1 // 从集合中获取部分数据 println(array.head) // 1 println(array.tail) // ArrayBuffer(2, 3, 2, 1, 4, 5) println(array.tails) // println(array.last) // 5 println(array.init) // ArrayBuffer(1, 2, 3, 2, 1, 4) // 取前2个 println(array.take(2)) // ArrayBuffer(1, 2) // 后2个 println(array.takeRight(2)) // ArrayBuffer(4, 5) // 删除前3个 println(array.drop(3)) // ArrayBuffer(2, 1, 4, 5) // 删除后三个 println(array.dropRight(3)) // ArrayBuffer(1, 2, 3, 2) ``` #### 7.1.4 可变数组和不可变数组转换 ```scala val buffer = ArrayBuffer(1,2,3,4) val array = Array(4,5,6,7) // 将不可变数组转换为可变数组 val buffer1: mutable.Buffer[Int] = array.toBuffer // 将可变数组转换为不可变数组 val array1: Array[Int] = buffer.toArray ``` ### 7.2 Seq 集合 #### 7.2.1 不可变List ```scala // 一般用List val seq = Seq(1, 2, 3, 4) val list = List(1, 2, 3, 4) println(seq) // List(1, 2, 3, 4) println(list) // List(1, 2, 3, 4) // 数据操作 val ints = list :+ 5 val ints1 = 5 +: list println(ints) // List(1, 2, 3, 4, 5) println(ints1) // List(5, 1, 2, 3, 4) // Nil在集合中表示空集合 val inst2 = 1 :: 2 :: 3 :: Nil println(inst2) // List(1, 2, 3) ``` #### 7.2.2 可变List ```scala // 可变集合 val buffer = new ListBuffer[Int]() // 增加数据 buffer.append(1, 2, 3, 4, 3, 2, 1, 0) println(buffer) // 修改数据 buffer.update(1, 5) println(buffer) // 删除数据 buffer.remove(1) println(buffer) buffer.remove(0, 2) println(buffer) // 获取数据 println(buffer(0)) // 遍历集合 buffer.foreach(println) ``` #### 7.2.3 可变List和不可变List转换 ```scala // 可变集合 val buffer1 = ListBuffer(1, 2, 3, 4) val buffer2 = ListBuffer(1, 2, 3, 4) // 与不可变集合的转换 val buffer = ListBuffer(1, 2, 3, 4) val list = List(5, 6, 7, 8) // 可变集合转不可变集合 val list1 = buffer.toList // 不可变集合转换可变集合 val buffer3 = list.toBuffer ``` ### 7.3 Set 集合 #### 7.3.1 不可变Set ```scala val set1 = Set(1, 2, 3, 4, 3, 2, 1) val set2 = Set(5, 6, 7, 8) println(set1) // Set(1, 2, 3, 4) println(set2) // Set(5, 6, 7, 8) // 增加数据 val set3 = set1 + 5 + 6 val set4 = set1.+(9, 10, 7) println(set3) // Set(5, 1, 6, 2, 3, 4) println(set4) // Set(10, 1, 9, 2, 7, 3, 4) // 删除数据 val set5 = set1 - 1 - 2 println(set5) // Set(3, 4) ``` #### 7.3.2 可变Set ```scala val set1 = mutable.Set(1, 2, 3, 4) val set2 = mutable.Set(5, 6, 7, 8) // 增加数据 set1.add(5) println(set1) // Set(1, 5, 2, 3, 4) // 增加数据 set1.update(8, included = true) println(set1) // Set(1, 5, 2, 3, 4, 8) // 删除数据 set1.update(8, included = false) println(set1) // Set(1, 5, 2, 3, 4) // 删除数据 set1.remove(2) println(set1) // Set(1, 5, 3, 4) // 交集 val set3 = set1 & set2 println(set3) // Set(5) // 差集 val set4 = set1 &~ set2 println(set4) // Set(1, 3, 4) ``` ### 7.4 Map集合 可迭代的键值对(key/value)结构。所有的值都可以通过键来获取。Map 中的键都是唯一的。 #### 7.4.1 不可变Map ```scala val map1 = Map("a" -> 1, "b" -> 2, "c" -> 3) val map2 = Map("d" -> 4, "e" -> 5, "f" -> 6) // 添加数据 val map3 = map1 + ("d" -> 4) println(map3) // Map(a -> 1, b -> 2, c -> 3, d -> 4) // 删除数据 val map4 = map3 - "d" println(map4) // Map(a -> 1, b -> 2, c -> 3) // 创建空集合 val empty = Map.empty println(empty) // Map() // 获取指定key的值 val i = map1.apply("c") println(i) // 3 println(map1("c")) // 3 // 获取可能存在的key值 val maybeInt = map1.get("c") // 判断值是否存在 if (maybeInt.isEmpty) println(s"Empty, default num: ${maybeInt.getOrElse(0)}") else println(maybeInt.get) // 3 // 获取可能存在的key值, 如果不存在就使用默认值 println(map1.getOrElse("c", 0)) // 3 ``` #### 7.4.2 可变Map ```scala val map1 = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4) val map2 = mutable.Map("e" -> 5, "f" -> 6, "g" -> 7, "h" -> 8) // 添加数据 map1.put("g", 9) println(map1) // Map(b -> 2, d -> 4, g -> 9, a -> 1, c -> 3) // 修改 map1.update("g", 111) println(map1) // Map(b -> 2, d -> 4, g -> 111, a -> 1, c -> 3) map1("g") = 0 println(map1) // Map(b -> 2, d -> 4, g -> 0, a -> 1, c -> 3) // 删除数据 map1.remove("g") println(map1) // Map(b -> 2, d -> 4, a -> 1, c -> 3) // 清空 // map1.clear() // println(map1) // Map() // 转其他集合 val set: Set[(String, Int)] = map1.toSet val list: List[(String, Int)] = map1.toList val seq: Seq[(String, Int)] = map1.toSeq val array: Array[(String, Int)] = map1.toArray println(map1.keys) // Set(b, d, a, c) println(map1.keySet) // Set(b, d, a, c) println(map1.keysIterator) // println(map1.values) // HashMap(2, 4, 1, 3) println(map1.valuesIterator) // ``` ### 7.5 队列 队列的特点就是先进先出。进队和出队的方法分别为enqueue和dequeue ```scala val que = new mutable.Queue[String] // add que.enqueue("a", "b", "c") println(que) // Queue(a, b, c) que += "CJ" println(que) // Queue(a, b, c, CJ) // 获取元素 println(que.dequeue()) // a println(que.dequeue()) // b println(que.dequeue()) // c ``` ### 7.6 元组 ```scala // 创建元组,使用小括号 val tuple = (1, "CJ", 24) // 根据顺序号访问元组的数据 println(tuple._1) // 1 println(tuple._2) // CJ println(tuple._3) // 24 // 迭代器 val iterator = tuple.productIterator while (iterator.hasNext) { val value = iterator.next() print(value + " ") // 1 CJ 24 } println() // 根据索引访问元素 println(tuple.productElement(2)) // 24 // 如果元组的元素只有两个,那么我们称之为对偶元组,也称之为键值对 val tuple1 = "a" -> 1 val tuple2 = ("b", 2) // val kv: (String, Int) = ("a", 1) // val kv1: (String, Int) = "a" -> 1 // println( kv eq kv1 ) // false ``` ### 7.7 常用方法 - FoldScan ```scala val array = ArrayBuffer(1, 2, 3, 4, 5) val num = 5 val res1 = array.fold(5)(_ - _) println(array.scan(5)(_ - _)) // ArrayBuffer(5, 4, 2, -1, -5, -10) println(res1) // 20 // 1 - (2 - (3 - (4 - (5 - 5)))) val res2 = array.foldRight(5)(_ - _) println(res2) // -2 // println(1 - (2 - (3 - (4 - (5 - 5))))) // -2 println(array.scanRight(5)(_ - _)) // ArrayBuffer(-2, 3, -1, 4, 0, 5) ``` - reduce ```scala val buffer = ArrayBuffer(1, 2, 3, 4, 5) // val sum = buffer.reduce((x: Int, y: Int) => { // x + y // }) //简化 // val sum = buffer.reduce((x, y) => x + y) val sum = buffer.reduce(_ + _) println(sum) // 10 // reduceLeft val res1 = buffer.reduceLeft(_ - _) println(res1) // -8 // ReduceRight // reversed.reduceLeft[B]((x, y)=> op(y, x)) // 注意这里是 (y, x) val res2 = buffer.reduceRight(_ - _) println(res2) // 3 ``` - filter ```scala val array = ArrayBuffer(1, 2, 3, 4, 5) println(array.filter(num => { num % 2 != 0 })) // ArrayBuffer(1, 3, 5) ``` - groupBy ```scala val array = ArrayBuffer(1, 2, 3, 4, 5) // 根据指定规则对每一条数据进行分组 println(array.groupBy(num => { num % 2 })) // Map(1 -> ArrayBuffer(1, 3, 5), 0 -> ArrayBuffer(2, 4)) println(array.groupBy(_ % 2)) // Map(1 -> ArrayBuffer(1, 3, 5), 0 -> ArrayBuffer(2, 4)) ``` - sortBy ```scala val arr = ArrayBuffer("11", "12", "111", "25", "233") println(arr.sortBy(str => str)) // ArrayBuffer(11, 111, 12, 233, 25) println(arr.sortBy(str => str.toInt)) // ArrayBuffer(11, 12, 25, 111, 233) // 指定逆序 使用函数Curry化 println(arr.sortBy(str => str.toInt)(Ordering.Int.reverse)) // ArrayBuffer(233, 111, 25, 12, 11) ``` - sliding ```scala val list = List(1, 2, 3, 4, 5, 6, 7, 8) val iterator = list.sliding(3) while (iterator.hasNext) { val ints = iterator.next() println(ints) /* List(1, 2, 3) List(2, 3, 4) List(3, 4, 5) List(4, 5, 6) List(5, 6, 7) List(6, 7, 8) */ } val iterator1 = list.sliding(3, 2) while (iterator1.hasNext) { val ints = iterator1.next() println(ints) /* List(1, 2, 3) List(3, 4, 5) List(5, 6, 7) List(7, 8) */ } ``` ### 7.8 并行 Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。 ```scala val result1 = (0 to 100).map(x => Thread.currentThread().getName) val result2 = (0 to 100).par.map(x => Thread.currentThread().getName) println(result1) // Vector(main, main, main, main, main, main, main, ..., main) println(result2) // ParVector(scala-execution-context-global-12, scala-execution-context-global-12, scala-execution-context-global-17, ..., scala-execution-context-global-19) ``` ## 8. 模式匹配(重点) 模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明 ### 8.1 基本语法 ```scala val a: Int = 20 val b: Int = 10 val operator: Char = 'c' val result: Any = operator match { case '+' => a + b // 当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码 case '-' => a - b // 如果匹配不成功,继续执行下一个分支进行判断 case '*' => a * b // case '/' => a / b case _ => "illegal" // 如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句 // 如果所有case都不匹配,且不存在case _分支,那么会发生错误 } println(result) // illegal ``` ### 8.2 匹配规则 #### 8.2.1 常量匹配 ```scala // 匹配常量 def describe(x: Any) = x match { case 5 => "Int five" case "hello" => "String hello" case true => "Boolean true" case '+' => "Char +" } println(describe(5)) // Int five println(describe("hello")) // String hello println(describe(true)) // Boolean true println(describe('+')) // Char + // println(describe('0')) // MatchError 运行时 ``` #### 8.2.2 匹配类型 ```scala // 匹配类型 类型前面加变量名,这个变量就是将数据转换成指定类型的变量 def describe(x: Any) = x match { case i: Int => "Int" case s: String => "String" case m: List[Int] => "List" case c: Array[Int] => "Array[Int]" case someThing => "something else " + someThing } println(describe(24)) // Int println(describe("CJ")) // String println(describe(List(1, 3, 5, 2, 4))) // List // Scala中的泛型是不考虑泛型的 println(describe(List("1", "2", "3"))) // List // Array不同,这里的Array并不是真正的泛型,底层实际上还是数组 println(describe(Array("1", "2", "3"))) // something else [Ljava.lang.String;@45c8e616 println(describe(Array(3, 5, 8, 9))) // Array[Int] println(describe(114.514)) // something else 114.514 ``` #### 8.2.3 匹配数组 ```scala // 匹配数组 for (arr <- Array( Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1), Array("hello", 90))) { // 对一个数组集合进行遍历 val result = arr match { case Array(0) => "0" //匹配Array(0) 这个数组 case Array(x, y) => x + "," + y //匹配有两个元素的数组,然后将将元素值赋给对应的x,y case Array(0, _*) => "以0开头的数组" //匹配以0开头和数组 case _ => "something else" } println("result = " + result) } /* result = 0 result = 1,0 result = 以0开头的数组 result = something else result = something else result = hello,90 */ ``` #### 8.2.4 匹配列表 ```scala // 匹配列表 for (list <- Array( List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0), List(88))) { val result = list match { case List(0) => "0" //匹配List(0) case List(x, y) => x + "," + y //匹配有两个元素的List case List(0, _*) => "0 ..." case _ => "something else" } println(result) /* 0 1,0 0 ... something else something else */ } ``` ```scala // val list: List[Int] = List(1, 2, 5, 6, 7) // 1-2-List(5, 6, 7) // val list: List[Int] = List(1, 2) // 1-2-List() val list: List[Int] = List(1) // something else list match { case first :: second :: rest => println(first + "-" + second + "-" + rest) case _ => println("something else") } ``` #### 8.2.5 匹配元组 ```scala for (tuple <- Array( (0, 1), (1, 0), (1, 1), (1, 0, 2))) { val result = tuple match { case (0, _) => "0 ..." //是第一个元素是0的元组 case (y, 0) => "" + y + "0" // 匹配后一个元素是0的对偶元组 case (a, b) => "" + a + " " + b case _ => "something else" //默认 } println(result) } ``` #### 8.2.6 匹配对象 ```scala // Attribute -> Object val user = User("CJ", 24) user match { case User("CJ", 24) => println("the user is CJ") case _ => println("other user") } } class User { var name: String = _ var age: Int = _ } object User { def unapply(user: User): Option[(String, Int)] = { // Option((user.name, user.age)) } def apply(name: String, age: Int) = { val user = new User() user.name = name user.age = age user } } ``` ### 8.3 模式匹配应用场景 #### 8.3.1 匹配变量 ```scala val (x, y) = (1, 2) println(s"x=$x,y=$y") // x=1,y=2 val Array(first, second, _*) = Array(1, 7, 2, 9) println(s"first=$first,second=$second") // first=1,second=7 ``` #### 8.3.2 匹配循环 ```scala // 匹配循环 val map = Map("A" -> 1, "B" -> 2, "C" -> 3) for ((k, v) <- map) { // //直接将map中的k-v遍历出来 println(s"$k-$v") } ``` ```scala //遍历value=0的 k-v ,如果v不是2,过滤 for ((k, 2) <- map) { println(k + " --> " + 2) // B --> 2 } // if v == 0 是一个过滤的条件 for ((k, v) <- map if v >= 2) { println(k + " ---> " + v) // A->1 和 c->33 } ``` ### 8.4 匹配参数 ```scala val list = List( ("a", 1), ("b", 2), ("c", 3) ) val list1 = list.map { case (k, v) => { (k, v * 2) } } println(list1) // List((a,2), (b,4), (c,6)) ``` ### 8.5 偏函数 偏函数,其实就是对集合中符合条件的数据进行处理的函数 ```scala // 声明 val pf: PartialFunction[Int, String] = { case 1 => "one" } println(List(1, 2, 3, 4).collect(pf)) // List(one) ``` ```scala // 将该List(1,2,3,4,5,6,"test")中的Int类型的元素加一,并去掉字符串 val list = List(1, 2, 3, 4, 5, 6, "test") val result = list.collect { case i: Int => i + 1 } println(result) // List(2, 3, 4, 5, 6, 7) ``` ## 9. 异常 ```scala try { var n = 10 / 0 } catch { case ex: ArithmeticException => { // 发生算术异常 println("发生算术异常") } case ex: Exception => { // 对异常处理 println("发生了异常1") } } finally { println("finally") } ``` Scala中的异常不区分所谓的编译时异常和运行时异常,也无需显示抛出方法异常,所以Scala中没有throws关键字。 如果Java程序调用scala代码,如何明确异常? 增加注解 `@throws(Exception)` ## 10. 泛型 ```scala // 泛型不可变 val test1 : Test[User] = new Test[User] // OK // val test2 : Test[User] = new Test[Parent] // Error // val test3 : Test[User] = new Test[SubUser] // Error } class Test[T] { } class Parent { } class User extends Parent{ } class SubUser extends User { } ``` ```scala // 泛型协变 val test1: Test[User] = new Test[User] // OK // val test2 : Test[User] = new Test[Parent] // Error val test3: Test[User] = new Test[SubUser] // OK } class Test[+T] { } class Parent { } class User extends Parent { } class SubUser extends User { } ``` ```scala // 泛型逆变 val test1: Test[User] = new Test[User] // OK val test2: Test[User] = new Test[Parent] // OK // val test3: Test[User] = new Test[SubUser] // Error } class Test[-T] { } class Parent { } class User extends Parent { } class SubUser extends User { } ``` ```scala // 泛型上限 val parent = new Parent val user = new User val subUser = new SubUser // test[Parent](parent) // ERROR test[User](user) // OK test[SubUser](subUser) // OK } def test[A <: User](a: A): Unit = { println(a) } class Parent { } class User extends Parent { } class SubUser extends User { } ``` ```scala // 泛型下限 val parent = new Parent val user = new User val subUser = new SubUser test[Parent](parent) // OK test[User](user) // OK // test[SubUser](subUser) // Error } def test[A >: User](a: A): Unit = { println(a) } class Parent { } class User extends Parent { } class SubUser extends User { } ``` ```scala // 上下文限定 // 上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同, // 使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数, // 需要通过implicitly[Ordering[A]]获取隐式变量, // 如果此时无法查找到对应类型的隐式变量,会发生出错误。 def f[A: Test](a: A) = println(a) implicit val test: Test[User] = new Test[User] f(new User) } class Test[T] { } class Parent { } class User extends Parent { } class SubUser extends User { } ``` ## 11. 正则表达式 ```scala // 构建正则表达式 val pattern = "Scala".r val str = "Scala is Scalable Language" // 匹配字符串 - 第一个 println(pattern findAllIn str) // // 匹配字符串 - 所有 val iterator: Regex.MatchIterator = pattern findAllIn str while (iterator.hasNext) { println(iterator.next()) // Scala // Scala } println("***************************") // 匹配规则:大写,小写都可 val pattern1 = new Regex("(S|s)cala") val str1 = "Scala is scalable Language" println((pattern1 findAllIn str1).mkString(",")) ```