Scala2.12
视频地址
1 入门
1.1 发展历史
…
1.2 Scala 和 Java
Scala = Java++
- 编写代码
- 使用scalac编译成.class字节码文件
- scala + .class文件 执行代码
1.3 特点
1.4 安装
视频地址
注意配置好环境变量
简单代码
1.5 编译文件
编译scala文件会产生两个.class文件
使用java命令执行scala编译出的class文件,报错:
正确方法如下
1.6 IDEA使用
创建maven项目
安装scala插件
创建java 和scala目录同时设置成源代码目录
右击项目名 -> 添加框架支持
1.7 HelloWorld
创建包和类 输入main即可自动补全
基本格式
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()}
}
1.9 反编译
2 变量和数据类型
2.1 注释
和java完全一样
2.2 变量和常量
原则
- 声明变量时,类型可以省略,编译器自动推导,即类型推导,var a1 = 10
- 类型确定后,就不能修改,说明Scala是强数据类型语言
- 变量声明时,必须要有初始值
- 在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改。
2.3 命名规范
基本与Java
一致
- 以字母或者下划线开头,后接字母、数字、下划线
- 以操作符开头,且只包含操作符(+ - * / # !等)
- 用反引号`…`包括的任意字符串,即使是 Scala 关键字(39 个)也可以
关键字
2.4 字符串输出
基本用法
- 字符串通过+连接
- printf 用法:字符串,通过%传值。
- 字符串模板(插值字符串):通过$获取变量值
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}""".stripMarginprintln(s1)val s2 = s"name=$name"println(s2)
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)
2.6 数字类型
Java
scala
2.7 整数类型
Byte、Short、Int、Long
定义不要超过类型的范围
// 正确var n1:Byte = 127var 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 等等。
Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加‘f’或‘F’。
// 建议,在开发中需要高精度小数时,请选择 Doublevar n7 = 2.2345678912fvar n8 = 2.2345678912
2.9 字符类型
字符类型可以表示单个字符,字符类型是 Char。
- 字符常量是用单引号 ’ ’ 括起来的单个字符。
- \t :一个制表位,实现对齐的功能
- \n :换行符
- \\ :表示\
- \" :表示"
虽然IDEA会报错 但是默认会自动进行强制类型转换
这样写就会报错
2.10 Boolean类型
同Java
2.11 Unit 、Null 、 Nothing
Unit
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()}
2.12 类型转换
基本同Java
object TestValueTransfer {def main(args: Array[String]): Unit = {//(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有
数据转换成精度大的那种数值类型,然后再进行计算。var n = 1 + 2.0println(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 = 1var 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.toIntvar 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类型转换
- 基本类型转 String 类型(语法:将基本类型的值+“” 即可)
- String 类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
例
def main(args: Array[String]): Unit = {var n: Int = 130var b: Byte = n.toByteprintln(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 中其实是没有运算符的,所有运算符都是方法。
- 当调用对象的方法时,
点.
可以省略 - 如果函数参数只有一个,或者没有参数,()可以省略
object TestOpt {def main(args: Array[String]): Unit = {// 标准的加法运算val i:Int = 1.+(1)// (1)当调用对象的方法时,.可以省略val j:Int = 1 + (1)// (2)如果函数参数只有一个,或者没有参数,()可以省略val k:Int = 1 + 1println(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// Scalaprintln("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 3j = 4 - i
} {println("i=" + i + " j=" + j)
}
==================================
for (i <- 1 to 3) {var j = 4 - iprintln("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循环控制
- 与 for 语句不同,while 语句没有返回值,即整个 while 语句的结果是
Unit
类型() - 因为 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 函数基本语法
// 定义一个函数,实现将传入的名称打印出来。
object TestFunction {
def main(args: Array[String]): Unit = {// (1)函数定义def f(arg: String): Unit = {println(arg)}// (2)函数调用// 函数名(参数)f("hello world")}
}
5.1.2 函数和方法的区别
概念
- 为完成某一功能的程序语句的集合,称为函数。
- 类中的函数称之方法。
案例实操
- Scala 语言可以在任何的语法结构中声明任何的语法
- 函数没有重载和重写的概念;
方法
可以进行重载
和重写
- 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 参数函数
- 可变参数
- 如果参数列表中存在多个参数,那么可变参数一般放置在最后
- 参数默认值,一般将有默认值的参数放置在参数列表的后面
- 带名参数
object TestFunction {def main(args: Array[String]): Unit = {// (1)可变参数def test( s : String* ): Unit = {println(s)}// 有输入参数:输出 Arraytest("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 函数至简原则
能省就省
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
- 如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(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 高阶函数
函数作为参数输入
_
的用法
(3) f中传入一个函数,函数的参数为name,方法体为输出name
(4) 可以知道f中传入的函数就是将一个参数输出,无论参数的名字叫什么,所以可以用_代替参数名
// 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
函数作为函数的返回值返回
// 函数f5的参数为空,它的返回值为一个输入参数为Int,返回值为空的函数
def f5(): Int=>Unit = {def f6(a: Int): Unit = {println("f6调用 " + a)}f6 // 将函数直接返回
}
// 调用函数,相当于f5()返回的是一个函数名,后面的(25)是参数
f5()(25)
5.2.2 匿名函数
(x:Int)=>{函数体}
至简原则
- 参数的类型可以省略,会根据形参进行自动的推导
- 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
- 匿名函数如果只有一行,则大括号也可以省略
- 如果参数只出现一次,则参数省略且后面参数可以用_代替
- 如果执行的是一步操作,那么可以直接省略_
例
简化:
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(", "))}
定义一个匿名函数,并将它作为值赋给变量 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,返回值为Booleandef 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 = 10def 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})
案例:自定义while循环
使用柯里化最容易理解,while(代码块)(代码块)
package chapter05object 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){opmyWhile(condition)(op)}}doLoop _}println("=================")n = 10myWhile(n >= 1){println(n)n -= 1}// 3. 用匿名函数实现def myWhile2(condition: =>Boolean): (=>Unit)=>Unit = {// 内层函数需要递归调用,参数就是循环体op => {if (condition){opmyWhile2(condition)(op)}}}println("=================")n = 10myWhile2(n >= 1){println(n)n -= 1}// 3. 用柯里化实现====最容易理解def myWhile3(condition: =>Boolean)(op: =>Unit): Unit = {if (condition){opmyWhile3(condition)(op)}}println("=================")n = 10myWhile3(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
}
6 面向对象
Scala 的面向对象思想和 Java 的面向对象思想和概念是一致的。
Scala 中语法和 Java 不同,补充了更多的功能。
6.1 Scala包
6.1.1 包的命名
同java
6.1.2 包语句
有两种风格
- com.jaken.scala
- 嵌套风格
package com{package jaken{package scala{}}
}
优点:
- 一个源文件中可以声明多个 package
- 子包中的类可以直接访问父包中的内容,而无需导包
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 中可以写多
个类。
- Scala 语法中,类并不声明为
public
,所有这些类都具有公有可见性(即默认就是 public) - 一个 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 访问权限
- Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字。
- private 为私有权限,只在
类的内部和伴生对象
中可用。 - protected 为受保护权限,Scala 中受保护权限比 Java 中
更严格
,同类
、子类
可以访问,同包无法访问
。 private[包名]
增加包访问权限,包名下的其他类也可以使用
例
在子类中idCard由于是私有属性 所以不能访问
在实例对象中,
protect
对象不能访问
6.3.2 构造方法
object Test05_Constructor {def main(args: Array[String]): Unit = {// 调用的主构造方法val student1 = new Student1//调用的一般方法student1.Student1()//调用的辅助构造方法1val student2 = new Student1("alice")//调用的辅助构造方法2val student3 = new Student1("bob", 25)}
}// 定义一个类,其实也是主构造方法
class Student1() {// 定义属性var name: String = _var age: Int = _println("1. 主构造方法被调用")// 声明辅助构造方法1def this(name: String) {// 首先必须调用主构造器this() println("2. 辅助构造方法一被调用")this.name = nameprintln(s"name: $name age: $age")}// 声明辅助构造方法2def this(name: String, age: Int){//调用构造方法1this(name)println("3. 辅助构造方法二被调用")this.age = ageprintln(s"name: $name age: $age")}
// 不同于JAVA,这并不是一个构造方法,而是一个普通的方法def Student1(): Unit = {println("一般方法被调用")}
}
运行结果
6.3.3构造方法参数
实操
object Test06_ConstructorParams {def main(args: Array[String]): Unit = {val student2 = new Student2student2.name = "alice"student2.age = 18println(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 = 21val 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")}
}
运行结果
6.4 继承
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 Teacherteacher.printInfo()def personInfo(person: Person7): Unit = {person.printInfo()}println("=========================")val person = new Person7personInfo(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 = namethis.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")}
}
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");}
}
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")}
}
6.6 抽象类
基本语法
- 定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
- 定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
- 定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
package chapter06object Test09_AbstractClass {def main(args: Array[String]): Unit = {val student = new Student9student.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")}
}
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: Stringdef 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 chapter06object Test13_Trait {def main(args: Array[String]): Unit = {val student: Student13 = new Student13student.sayHello()student.study()student.dating()student.play()}
}// 定义一个父类
class Person13 {val name: String = "person"var age: Int = 18def sayHello(): Unit = {println("hello from: " + name)}def increase(): Unit = {println("person increase")}
}// 定义一个特质
trait Young {// 声明抽象和非抽象属性var age: Intval 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")}
}
6.8.1 多特质(mixin)动态混入
package chapter06object Test14_TraitMixin {def main(args: Array[String]): Unit = {val student = new Student14student.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 = 0def increase(): Unit
}trait Talent {def singing(): Unitdef 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 += 1println(s"student $name knowledge increased: $amount")}
}
6.8.2 特质的叠加
由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法
(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。 冲突分为以下两种:
解决这类冲突问题,直接在类(Sub)中重写冲突方法。
所谓的“钻石问题”,解决这类冲突问题,Scala 采用了
特质叠加
的策略。
package chapter06object Test15_TraitOverlying {def main(args: Array[String]): Unit = {// 钻石问题特征叠加val myFootBall = new MyFootBallprintln(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()
}
案例中的 super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质, 即,MyClass 中的 super 指代
Color
,Color 中的 super 指代Category
,Category 中的 super 指代Ball
。
6.8.3 指定super
除了上面的推断外,也可以指定
使用哪个父类的方法
6.8.4 特质和抽象类的区别
- 优先使用特质。一个类扩展
多个特质
是很方便的,但却只能扩展一个
抽象类。 - 如果你需要
构造函数参数
,使用抽象类
。因为抽象类可以定义带参数的构造函
数, 而特质不行(有无参构造)。
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来调用Userprintln(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 = Stringval a: MyString = "abc"println(a)
}// 2. 测试枚举类,直接引用就行
println(WorkDay.MONDAY)
7 集合
- Scala 的集合有三大类:
序列 Seq
、集 Set
、映射 Map
,所有的集合都扩展自Iterable
特质。 - 对于几乎所有的
集合类
,Scala 都同时提供了可变
和不可变
的版本,分别位于以下两 个包(scala.collection.immutable
、scala.collection.mutable
) - Scala
不可变集合
,就是指该集合对象不可修改
,每次修改就会返回一个新对象
,而 不会对原对象进行修改。类似于 java 中的 String 对象 - 可变集合,就是这个集合可以
直接对原对象进行修改
,而不会返回新的对象
。类似 于 java 中 StringBuilder 对象
建议
在操作集合的时候,不可变用符号
,可变用方法
7.1 不可变集合继承图
7.2 可变集合继承图
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操作
fold
和reduce
的区别就是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))), 6println("===========================")
// 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中两个类型必须一样,所以使用foldLeftval map3 = map1.foldLeft(map2)(// mergedMap表示结果,初始值为map2,kv表示的是map1中的元素(mergedMap, kv) => {val key = kv._1val value = kv._2println(kv._1+' '+kv._2)mergedMap(key) = mergedMap.getOrElse(key, 0) + valuemergedMap}
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,并排序取前3val 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
- 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句, 若此时没有 case _ 分支,那么会抛出 MatchError。
- 每个 case 中,不需要使用 break 语句,
自动中断 case
。 - match case 语句可以
匹配任何类型
,而不只是字面量。 - => 后面的代码块,直到下一个 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 = 13def matchDualOp(op: Char): Int = op match {case '+' => a + bcase '-' => a - bcase '*' => a * bcase '/' => a / bcase '%' => a % bcase _ => -1
}println(matchDualOp('+'))
println(matchDualOp('/'))
println(matchDualOp('\\'))println("=========================")
8.2 模式守卫
// 3. 模式守卫 也就是加了个判断
// 求一个整数的绝对值
def abs(num: Int): Int = {num match {case i if i >= 0 => icase i if i < 0 => -i}
}println(abs(67))
println(abs(0))
println(abs(-24))
8.3 匹配类型
package chapter08object 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 " + icase 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 + ", " + ycase List(0, _*) => "List(0, ...)"// list中有一个元素case List(a) => "List(a): " + acase _ => "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 + ", " + bcase (0, _) => "(0, _)"case (a, 1, _) => "(a, 1, _) " + acase (x, y, z) => "(x, y, z) " + x + " " + y + " " + zcase _ => "something else"}println(result)}}
}
8.4 应用时匹配
package chapter08object 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或者valuefor ((word, _) <- list)println(word)println("-----------------------")// 2.4 可以指定某个位置的值必须是多少for (("a", count) <- list){println(count)}}
}
8.5 匹配对象
package chapter08object 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 的集合,这就是通过模式 匹配实现的。
package chapter08object 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)// 偏函数的应用,求绝对值// 对输入数据分为不同的情形:正、负、0val 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 异常
需要注意以下几点:
- Scala 没有“checked(编译期)”异常,即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
- 所有异常都是
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 ndef 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 ndef 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 协变和逆变
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 = 1val 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 结果为-1println(f[Int](3,4))
}
9 异常
需要注意以下几点:
- Scala 没有“checked(编译期)”异常,即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
- 所有异常都是
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 ndef 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 ndef 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 = 1val 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 结果为-1println(f[Int](3,4))
}