【大数据】Scala学习笔记
第 1 章 scala的概述1
1.1 学习sdala的原因 1
1.2 Scala语言诞生小故事 1
1.3 Scala 和 Java 以及 jvm 的关系分析图 2
1.4 Scala语言的特点 3
1.5 Windows下搭建Scala开发环境 4
1.6 Linux下搭建Scala开发环境 5
1.7 Scala开发工具的介绍 8
1.7.1 idea工具的介绍 8
1.7.2 Scala插件安装 8
1.8 scala的开发的快速入门 10
1.8.1 IDE工具Idea 来开发 “hello,world” 10
1.8.2 Scala程序反编译-说明scala程序的执行流程 15
1.8.3 使用java写了一段模拟的代码 17
1.8.4 课堂小练习 18
1.8.5 Scala执行流程分析 18
1.8.6 Scala程序开发注意事项(重点) 19
1.9 Scala语言转义字符 19
1.10 Scala语言输出的三种方式 21
1.10.1 基本介绍 21
1.10.2 应用案例 21
1.11 Scala源码的查看的关联 22
1.12 注释(comment) 23
1.12.1 介绍: 23
1.12.2 Scala中的注释类型 23
1.12.3 文档注释的案例 24
1.13 规范的代码风格 25
1.13.1 正确的注释和注释风格: 25
1.13.2 规范代码的几点要求 25
1.14 Scala官方编程指南 25
第 2 章 变量28
2.1 变量的介绍 28
2.1.1 概念 28
2.1.2 变量使用的基本步骤 28
2.2 Scala变量使用案例入门 28
2.3 Scala变量使用说明 29
2.3.1 变量声明基本语法 29
2.3.2 注意事项 29
2.4 程序中 +号的使用 32
2.5 数据类型 33
2.5.1 scala数据类型的介绍 33
2.6 scala数据类型体系一览图 34
2.6.1 一览图 34
2.6.2 对scala数据类型体系总结 35
2.7 整数类型 36
2.7.1 基本介绍 36
2.7.2 整型的类型 36
2.7.3 应用案例 36
2.7.4 整型的使用细节 37
2.8 浮点类型 39
2.8.1 基本介绍 39
2.8.2 浮点型的分类 39
2.8.3 浮点型使用细节 39
2.9 字符类型(Char) 41
2.9.1 基本介绍 41
2.9.2 案例演示: 41
2.9.3 字符类型使用细节 41
2.9.4 字符类型本质探讨 42
2.10 布尔类型:Boolean 43
2.10.1 基本介绍 43
2.11 Unit类型、Null类型和Nothing类型 43
2.11.1 基本说明 43
2.11.2 使用细节和注意事项 44
2.12 值类型转换 45
2.12.1 值类型隐式转换 45
2.12.2 高级隐式转换和隐式函数 48
2.12.3 强制类型转换 48
2.13 值类型转换-课堂练习题 49
2.14 值类型和String类型的转换 50
2.14.1 介绍 50
2.14.2 基本类型转String类型 50
2.14.3 String类型转基本数据类型 50
2.14.4 注意事项 51
2.15 标识符的命名规范 51
2.15.1 标识符概念 51
2.15.2 标识符的命名规则(记住) 51
2.15.3 标识符举例说明 52
2.15.4 标识符命名注意事项 53
2.15.5 关键字介绍 53
第 3 章 运算符54
3.1 运算符介绍 54
3.2 算术运算符一览 54
3.2.1 案例演示 54
3.2.2 细节说明 55
3.2.3 课堂练习 56
3.3 关系运算符(比较运算符) 57
3.3.1 一览图: 57
3.3.2 案例演示 58
3.3.3 细节说明 58
3.4 逻辑运算符 58
3.4.1 一览图 58
3.5 赋值运算符 59
3.5.1 位运算符 60
3.6 运算符的特别说明 61
3.7 运算符优先级 62
3.8 键盘输入语句 63
3.8.1 介绍 63
3.8.2 步骤 : 63
3.8.3 案例演示: 64
第 4 章 流程控制67
4.1 分支控制if-else 67
4.1.1 分支控制if-else介绍 67
4.1.2 单分支 67
4.1.3 单分支对应流程图 68
4.1.4 双分支 68
4.1.5 多分支 70
4.1.6 分支控制if-else 注意事项 73
4.2 嵌套分支 75
4.2.1 基本介绍 75
4.2.2 基本语法 75
4.2.3 应用案例1 75
4.2.4 多分支的案例应用案例 76
4.3 switch分支结构 77
4.4 for循环控制 77
4.4.1 基本介绍 77
4.4.2 范围数据循环方式1 77
4.4.3 范围数据循环方式2 78
4.4.4 循环守卫 79
4.4.5 引入变量 80
4.4.6 嵌套循环 81
4.4.7 循环返回值 82
4.4.8 使用花括号{}代替小括号() 82
4.4.9 注意事项和细节说明 84
4.4.10 for循环练习题(学员先做) 85
4.5 while循环控制 86
4.5.1 基本语法 86
4.5.2 while循环应用实例 86
4.5.3 注意事项和细节说明 87
4.6 do..while循环控制 87
4.6.1 注意事项和细节说明 88
4.6.2 课堂练习题【学员先做】 88
4.7 多重循环控制 89
4.7.1 介绍: 89
4.7.2 应用实例: 89
4.8 while循环的中断 92
4.8.1 基本说明 92
4.8.2 应用案例 92
4.8.3 课堂练习题: 94
第 5 章 函数式编程基础96
5.1 函数式编程内容及授课顺序说明 96
5.2 函数式编程授课顺序即oop和函数式编程关系 96
5.2.1 几个概念的说明 97
5.3 函数式编程介绍 98
5.4 为什么需要函数 98
5.5 函数介绍 98
5.5.1 基本语法 99
5.5.2 函数的快速入门 99
5.6 函数-调用机制 99
5.6.1 函数-调用过程 100
5.7 函数-递归调用 101
5.7.1 基本介绍 101
5.7.2 递归调用快速入门 101
5.7.3 函数递归需要遵守的重要原则: 102
5.7.4 递归课堂练习题 102
5.8 函数注意事项和细节讨论 103
5.9 函数练习题 108
5.10 过程 109
5.10.1 基本介绍 109
5.10.2 案例说明: 109
5.11 惰性函数 110
5.11.1 看一个应用场景 110
5.11.2 画图说明[大数据推荐系统] 110
5.11.3 Java实现懒加载的代码 110
5.11.4 介绍 111
5.11.5 案例演示 111
5.11.6 注意事项和细节 112
5.12 异常 112
5.12.1 介绍 113
5.12.2 Java异常处理回顾 113
5.12.3 Java异常处理的注意点. 113
5.12.4 Scala异常处理举例 114
5.12.5 scala异常的小结和注意事项 114
5.13 函数的课堂练习题 115
第 6 章 面向对象编程基础部分117
6.1 类与对象 117
6.1.1 看一个养猫猫问题 117
6.1.2 快速入门-面向对象的方式解决养猫问题 117
6.1.3 如何定义类 118
6.1.4 属性 119
6.1.5 属性的高级部分 121
6.1.6 如何创建对象 122
6.1.7 类和对象的内存分配机制 124
6.1.8 方法 125
6.1.9 课堂练习题 126
6.2 类与对象应用实例 128
6.2.1 小狗案例 128
6.3 构造器 130
6.3.1 看一个需求 130
6.3.2 回顾-Java构造器基本语法 131
6.3.3 回顾-Java构造器的特点 131
6.3.4 Scala构造器的介绍 132
6.3.5 Scala构造器的基本语法 132
6.3.6 快速入门 133
6.3.7 Scala构造器注意事项和细节 134
6.4 属性高级 139
6.4.1 构造器参数 139
6.4.2 Bean属性 141
6.5 对象创建的流程分析 143
6.5.1 看一个案例 143
6.5.2 流程分析(面试题) 143
第 7 章 面向对象编程(中级)145
7.1 包 145
7.1.1 看一个应用场景 145
7.1.2 回顾-Java包的三大作用 145
7.1.3 回顾-Java打包命令 145
7.1.4 打包的本质分析 146
7.1.5 快速入门 146
7.1.6 回顾-Java如何引入包 146
7.1.7 回顾-Java包的特点 147
7.1.8 Scala包的基本介绍 147
7.1.9 Scala包快速入门 147
7.1.10 Scala包的特点概述 147
7.1.11 Scala包的命名 149
7.1.12 Scala会自动引入的常用包 149
7.1.13 Scala包注意事项和使用细节 150
7.1.14 包对象 153
7.1.15 包对象的应用案例 154
7.1.16 包对象的注意事项 154
7.2 包的可见性 155
7.2.1 Scala中包的可见性介绍: 155
7.2.2 Scala中包的可见性和访问修饰符的使用 155
7.3 包的引入 157
7.3.1 Scala引入包的细节和注意事项 157
7.4 面向对象编程方法-抽象 159
7.4.1 如何理解抽象 159
7.5 面向对象编程三大特征 162
7.5.1 基本介绍 162
7.5.2 封装介绍 162
7.5.3 封装的理解和好处 163
7.5.4 如何体现封装 163
7.5.5 封装的实现步骤 163
7.5.6 快速入门案例 164
7.5.7 封装的课堂练习题 165
7.5.8 Scala封装的注意事项和细节 165
7.6 面向对象编程-继承 166
7.6.1 Java继承的简单回顾 166
7.6.2 继承基本介绍和示意图 166
7.6.3 Scala继承的基本语法 167
7.6.4 Scala继承快速入门 167
7.6.5 Scala继承给编程带来的便利 169
7.6.6 子类继承了什么,怎么继承了? 169
7.6.7 重写方法 171
7.6.8 Scala中类型检查和转换 172
7.6.9 Scala中超类的构造 177
7.6.10 覆写字段 182
7.6.11 抽象类 187
7.6.12 Scala抽象类使用的注意事项和细节讨论 189
7.6.13 匿名子类 190
7.6.14 课后练习题 192
第 8 章 面向对象编程-高级194
8.1 静态属性和方法 194
8.1.1 静态属性-提出问题 194
8.1.2 回顾下Java的静态概念 194
8.1.3 Scala中静态的概念-伴生对象 194
8.1.4 伴生对象的快速入门 195
8.1.5 伴生对象的小结 196
8.1.6 伴生对象解决小孩游戏问题 198
8.1.7 apply方法的补充 199
8.2 单例对象 200
8.2.1 什么是单例对象 200
8.2.2 回顾-Java单例对象 201
8.2.3 Scala中单例对象 203
8.2.4 单例模式的面试题 203
8.3 接口 204
8.3.1 回顾Java接口 204
8.3.2 Scala接口的介绍 205
8.4 特质(trait) 205
8.4.1 trait 的声明 205
8.4.2 Scala中trait 的使用 206
8.4.3 特质的快速入门案例 206
8.4.4 特质trait 的再说明 209
8.4.5 带有特质的对象,动态混入 211
8.4.6 scala创建对象的四种形式 214
8.4.7 叠加特质 214
8.4.8 在特质中重写抽象方法 217
8.4.9 当作富接口使用的特质 220
8.4.10 特质中的具体字段 221
8.4.11 特质构造顺序 221
8.4.12 扩展类的特质 222
8.4.13 自身类型 224
8.5 嵌套类 225
8.5.1 基本介绍 225
8.5.2 类型投影 229
8.6 数组的基本使用 230
第 9 章 隐式转换和隐式参数232
9.1 隐式转换 232
9.1.1 提出问题 232
9.1.2 隐式函数基本介绍 232
9.1.3 隐式函数快速入门 232
9.1.4 隐式函数的底层工作原理 233
9.1.5 隐式转换的注意事项和细节 233
9.2 隐式转换丰富类库功能 234
9.2.1 基本介绍 234
9.2.2 分析解决方案 234
9.2.3 快速入门案例 234
9.3 隐式值 235
9.3.1 基本介绍 235
9.3.2 应用案例 235
9.3.3 课堂测试题 236
9.3.4 课堂练习 236
9.4 隐式类 237
9.4.1 基本介绍 237
9.4.2 隐式类使用有如下几个特点: 238
9.4.3 应用案例 238
9.5 隐式的转换时机 239
9.6 隐式解析机制 239
9.7 隐式转换的前提 239
第 10 章 数据结构上-集合241
10.1 数据结构的特点 241
10.1.1 scala集合基本介绍 241
10.1.2 可变集合和不可变集合举例 241
10.2 不可变集合继承层次一览 242
10.3 Scala可变集合继承关系一览图 244
10.4 数组-定长数组(声明泛型) 245
10.4.1 第一种方式定义数组 245
10.4.2 第二种方式定义数组 246
第 1 章 scala的概述
1.1 学习sdala的原因
1.2 Scala语言诞生小故事
1.3 Scala 和 Java 以及 jvm 的关系分析图
1.4 Scala语言的特点
Scala是一门以java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起
的静态类型编程语言。
1) Scala 是一门多范式 (multi-paradigm) 的编程语言,Scala支持面向对象和函数式编程
2) Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接。[案例演示]
3) scala 单作为一门语言来看, 非常的简洁高效 (三元运算, ++ , --)
4) Scala 在设计时,马丁·奥德斯基 是参考了Java的设计思想,可以说Scala是源于java,同时马丁·奥德斯基 也加入了自己的思想,将函数式编程语言的特点融合到JAVA中, 因此,对于学习过Java的同学,只要在学习Scala的过程中,搞清楚Scala 和 java相同点和不同点,就可以快速的掌握Scala这门语言
5) 快速有效掌握Scala的三点建议 [1. 学习scala的特有的语法 2. 区别 scala和Java 3. 如何规范使用scala]
1.5 Windows下搭建Scala开发环境
具体的步骤
1) 首先把jdk1.8 安装
2) 下载对应的scala安装文件 scala-2.11.8.zip
3) 解压 我这里解压到 d:/program
4) 配置scala 的环境变量
5) 测试一下, 输入scala的指令看看效果
6) OK!
1.6 Linux下搭建Scala开发环境
在实际开发中,我们的项目是部署到linux,因此,我们需要在Linux下搭建scala的环境。
具体的步骤如下:
1) 下载对应的scala的安装软件.scala-2.11.8.tgz
2) 通过远程登录工具,将安装软件上传到对应的linux系统(xshell5 xftp5)
3) mkdir /usr/local/scala 创建目录
4) tar -xvzf scala-2.11.8.tgz && mv scala-2.11.8 /usr/local/scala/ 将安装文件解压,并且移动到 /usr/local/scala
5) 配置环境变量 vim /etc/profile
在该文件中配置 scala的bin目录 /usr/local/scala/scala-2.11.8/bin
6) 测试一把
- Scala的REPL
1) 介绍
上面打开的scala命令行窗口,我们称之为REPL,是指:Read->Evaluation->Print->Loop,也称之为交互式解释器。
2) 说明
在命令行窗口中输入scala指令代码时,解释器会读取指令代码(R)并计算对应的值(E),然后将结果打印出来(P),接着循环等待用户输入指令(L)。从技术上讲,这里其实并不是一个解释器,而是指令代码被快速的编译成Java字节码并被JVM加载执行。最终将执行结果输出到命令行中
3) 示意图
1.7 Scala开发工具的介绍
1.7.1 idea工具的介绍
1.7.2 Scala插件安装
- 看老师的步骤
1) scala-intellij-bin-2017.2.6.zip
2) 建议该插件文件放到scala的安装目录
3) 将插件安装到idea
4) 先找到安装插件位置 file->setting...
5) 点击ok->apply -> 重启idea 即可
6) ok
1.8 scala的开发的快速入门
1.8.1 IDE工具Idea 来开发 “hello,world”
- 步骤1:file->new project -> 选择 maven
- 步骤2:
- 步骤3:
- 步骤4:默认下,maven不支持scala的开发,需要引入scala框架.
右键项目点击-> add framework support... ,在下图选择scala
注意:如果是第一次引入框架,Use libary 看不到,需要配置, 配置就是选择你的scala安装目录,然后工具就会自动识别,就会显示user libary .
- 步骤5: 创建项目的源文件目录
说明:右键main目录->创建一个diretory -> 写个名字(比如 scala)-> 右键scala 目录->mark directory -> 选择 source root 即可
- 步骤6: 开发一个Hello.scala的程序
- 创建包com.atguigu.chapter01 开发的程序
运行后,就可以看到输出
1.8.2 Scala程序反编译-说明scala程序的执行流程
//package com.atguigu.chapter01
//下面我们说明一下scala程序的一执行流程 //分析 //1. object 在底层会生成两个类 Hello , Hello$ //2. Hello 中有个main函数,调用 Hello$ 类的一个静态对象 MODULES$ /* public final class Hello { public static void main(String[] paramArrayOfString) { Hello$.MODULE$.main(paramArrayOfString); } } */ //3. Hello$.MODULE$. 对象时静态的,通过该对象调用Hello$的main函数 /* public void main(String[] args) { Predef..MODULE$.println("hello,scala"); } */ //4. 可以理解我们在main中写的代码在放在Hello$ 的main, 在底层执行scala编译器做了一个包装
object Hello { def main(args: Array[String]): Unit = { println("hello,scala") } }
|
1.8.3 使用java写了一段模拟的代码
package com.atguigu.chapter01;
public class Hello2 { public static void main(String[] args) { Hello2$.MODULE$.main(args); } }
final class Hello2$ { public static final Hello2$ MODULE$;
static { MODULE$ = new Hello2$(); }
public void main(String[] args) { System.out.println("hello,scala"); } //private Hello$() { MODULE$ = this; }
}
|
1.8.4 课堂小练习
1.8.5 Scala执行流程分析
1.8.6 Scala程序开发注意事项(重点)
1) Scala源文件以 “.scala" 为扩展名。
2) Scala程序的执行入口是main()函数。
3) Scala语言严格区分大小写。
4) Scala方法由一条条语句构成,每个语句后不需要分号(Scala语言会在每行后自动加分号),这也体现出Scala的简洁性。
5) 如果在同一行有多条语句,除了最后一条语句不需要分号,其它语句需要分号。
1.9 Scala语言转义字符
scala的转义字符。
- 应用案例
输出的结果:
C:\Users\Administrator\Desktop\SGG 韩顺平 scala 核心编程\day01\笔记
k"kk
你好
hello
u
- 课后练习
1.10 Scala语言输出的三种方式
1.10.1 基本介绍
1) 字符串通过+号连接(类似java)。
2) printf用法 (类似C语言)字符串通过 % 传值。
3) 字符串通过$引用(类似PHP)。
1.10.2 应用案例
package com.atguigu.chapter01.printdemo
object TestPrint { def main(args: Array[String]): Unit = { //使用+ var name : String = "tom" var sal : Double = 1.2 println("hello" + sal + name )
//使用格式化的方式printf printf("name=%s sal=%f\n", name, sal) //使用$引用的方式,输出变量,类似php println(s"第三种方式 name=$name sal = $sal") } }
|
1.11 Scala源码的查看的关联
在使用scala过程中,为了搞清楚scala底层的机制,需要查看源码,下面看看如果关联和查看Scala的源码包
1) 查看源码, 选择要查看的方法或者类, 输入 ctrl + b
当我们没关联源码时,看到如下图像:
2) 关联源码,看老师演示
步骤1:将我们的源码包拷贝到 scala/lib 文件夹下. scala-sources-2.12.4
步骤2: 关联即可,选中这个文件夹,进行关联, 最后,可以看到源码
1.12 注释(comment)
1.12.1 介绍:
用于注解说明解释程序的文字就是注释,注释提高了代码的阅读性;
注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现。
1.12.2 Scala中的注释类型
1) 单行注释
2) 多行注释
3) 文档注释
1.12.3 文档注释的案例
package com.atguigu.chapter01.comment2
object TestDemo {
/**
* @deprecated xxx
* @param args
*/
def main(args: Array[String]): Unit = {
//print("0k")
//ctrl +D ctrl +y
/*
println("ok1")
println("ok1")
println("ok1")
println("ok1")
println("ok1")
*/
println("ok")
}
}
- 使用scaladoc 指令生成了相应文档.
1.13 规范的代码风格
1.13.1 正确的注释和注释风格:
带看Scala源码
1.13.2 规范代码的几点要求
1) 正确的缩进和空白
2) 使用一次tab操作,实现缩进,默认整体向右边移动,时候用shift+tab整体向左移
3) 或者使用 ctrl + alt + L 来进行格式化 [演示]
4) 运算符两边习惯性各加一个空格。比如:2 + 4 * 5。
5) 一行最长不超过80个字符,超过的请使用换行展示,尽量保持格式优雅
1.14 Scala官方编程指南
还可以根据需要检索自己要查看的类或者函数。
第 2 章 变量
2.1 变量的介绍
2.1.1 概念
变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个房间的门
牌号,通过门牌号我们可以找到房间,而通过变量名可以访问到变量(值)。
2.1.2 变量使用的基本步骤
1) 声明/定义变量 (scala要求变量声明时初始化)
2) 使用
2.2 Scala变量使用案例入门
变量在内存中的布局
2.3 Scala变量使用说明
2.3.1 变量声明基本语法
var | val 变量名 [: 变量类型] = 变量值
说明:在scala中声明一个变量时,可以不指定类型,编译器根据值确定
2.3.2 注意事项
1) 声明变量时,类型可以省略(编译器自动推导,即类型推导)
2) 类型确定后,就不能修改,说明Scala 是强数据类型语言.
案例说明:
3) 在声明/定义一个变量时,可以使用var 或者 val 来修饰, var 修饰的变量可改变,val 修饰的变量不可改 [案例].
4) val修饰的变量在编译后,等同于加上final通过反编译看下底层代码
对应的底层的.class是
5) var 修饰的对象引用可以改变,val 修饰的则不可改变,但对象的状态(值)却是可以改变的。(比如: 自定义对象、数组、集合等等) [分析val好处]
6) 变量声明时,需要初始值
2.4 程序中 +号的使用
1) 当左右两边都是数值型时,则做加法运算
2) 当左右两边有一方为字符串,则做拼接运
3) 案例演示:
2.5 数据类型
2.5.1 scala数据类型的介绍
1) Scala 与 Java有着相同的数据类型,在Scala中数据类型都是对象,也就是说scala没有java中的原生类型
2) Scala数据类型分为两大类 AnyVal(值类型) 和 AnyRef(引用类型), 注意:不管是AnyVal还是AnyRef 都是对象。[案例演示 Int , Char]
3) 相对于java的类型系统,scala要复杂些!也正是这复杂多变的类型系统才让面向对象编程和函数式编程完美的融合在了一起
2.6 scala数据类型体系一览图
2.6.1 一览图
2.6.2 对scala数据类型体系总结
1) scala中一切数据都是对象,都是Any的子类。
2) scala中数据类型分为两大类 值类型(AnyVal) , 引用类型(AnyRef),不管是值类型还是引用类型都是对象。
3) scala数据类型仍然遵守低精度的值类型向高精度的值类型,自动转换(隐式转换)
4) scala中有两个特殊的类型 Null , 它只有一个实例就是null, 在scala中,也叫bottom class
5) scala中有个特殊的类型 Nothing, 它是用于在一个函数没有正常返回值使用 , 因为这样我们可以把抛出的返回值,返回给任何的变量或者函数.
2.7 整数类型
2.7.1 基本介绍
Scala的整数类型就是用于存放整数值的,比如 12 , 30, 3456等等
2.7.2 整型的类型
数据类型 |
描述 |
Byte [1] |
8位有符号补码整数。数值区间为 -128 到 127 |
Short [2] |
16位有符号补码整数。数值区间为 -32768 到 32767 |
Int [4] |
32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long [8] |
64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2的(64-1)次方-1 |
2.7.3 应用案例
2.7.4 整型的使用细节
1) Scala各整数类型有固定的表数范围和字段长度,不受具体OS的影响,以保证Scala程序的可移植性。
2) Scala的整型 常量/字面量 默认为 Int 型,声明Long型 常量/字面量 须后加‘l’’或‘L’ [反编译看]
反编译后的字节码文件
3) Scala程序中变量常声明为Int型,除非不足以表示大数,才使用Long
2.8 浮点类型
2.8.1 基本介绍
Scala的浮点类型可以表示一个小数,比如 123.4f,7.8 ,0.12等等
2.8.2 浮点型的分类
Float [4] |
32 位, IEEE 754标准的单精度浮点数 |
Double [8] |
64 位 IEEE 754标准的双精度浮点数 |
2.8.3 浮点型使用细节
- 应用案例
package com.atguigu.chapter01.floattest
object FloatDemo { def main(args: Array[String]): Unit = {
var n1 = 5.12E2 //5.12 * 10的2次方 println("n1=" + n1) var n2 = 5.12e-2 // 5.12 / 10的2次方 println("n2=" + n2)
//建议,在开发中需要高精度小数时,请选择Double var n3 = 2.2345678912f // var n4 = 2.2345678912 println("n3=" + n3) println("n4=" + n4)
//运行的结果 /* n1=512.0 n2=0.0512 n3=2.2345679 n4=2.2345678912 */
} }
|
2.9 字符类型(Char)
2.9.1 基本介绍
字符类型可以表示单个字符,字符类型是Char, 16位无符号Unicode字符(2个字节), 区间值为 U+0000 到 U+FFFF
2.9.2 案例演示:
2.9.3 字符类型使用细节
1) 字符常量是用单引号(‘ ’)括起来的单个字符。例如:var c1 = 'a‘ var c2 = '中‘ var c3 = '9'
2) Scala 也允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。例如:var c3 = ‘\n’ // '\n'表示换行符
3)可以直接给Char赋一个整数,然后输出时,会按照对应的unicode 字符输出 ['\u0061' 97]
4) Char类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码.
2.9.4 字符类型本质探讨
1) 字符型 存储到 计算机中,需要将字符对应的码值(整数)找出来
存储:字符——>码值——>二进制——>存储
读取:二进制——>码值——> 字符——>读取
2) 字符和码值的对应关系是通过字符编码表决定的(是规定好), 这一点和Java一样。
2.10 布尔类型:Boolean
2.10.1 基本介绍
1) 布尔类型也叫Boolean类型,Booolean类型数据只允许取值true和false
2) boolean类型占1个字节。
3) boolean 类型适于逻辑运算,一般用于程序流程控制[后面详解]:
- if条件控制语句;
- while循环控制语句;
- do-while循环控制语句;
- for循环控制语句
2.11 Unit类型、Null类型和Nothing类型
2.11.1 基本说明
Unit |
表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null |
null , Null 类型只有一个实例值 null |
Nothing |
Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。 当一个函数,我们确定没有正常的返回值,可以用Nothing 来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性) |
2.11.2 使用细节和注意事项
1) Null类只有一个实例对象,null,类似于Java中的null引用。null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal: 比如 Int, Float, Char, Boolean, Long, Double, Byte, Short)
2) Unit类型用来标识过程,也就是没有明确返回值的函数。
由此可见,Unit类似于Java里的void。Unit只有一个实例,
(),这个实例也没有实质的意义
3) Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。
2.12 值类型转换
2.12.1 值类型隐式转换
- 介绍
当Scala程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数据类型,这个就是自动类型转换(隐式转换)。
- 数据类型按精度(容量)大小排序为
- 案例演示
演示一下基本数据类型转换的基本情况。
- 注意事项和细节说明
自动类型转换细节说明
1) 有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。
2) 当我们把精度(容量)大 的数据类型赋值给精度(容量)小 的数据类型时,就会报错,反之就会进行自动类型转换。
3) (byte, short) 和 char之间不会相互自动转换。
4) byte,short,char 他们三者可以计算,在计算时首先转换为int类型。
5) 自动提升原则: 表达式结果的类型自动提升为 操作数中最大的类型
package com.atguigu.chapter02.transtest
object TransferDemo { def main(args: Array[String]): Unit = {
var n = 1 + 2.0 println(n) // n 就是Double
//当我们把精度(容量)大 的数据类型赋值给精度(容量)小 的数据类型时,就会报错 var n2 : Long = 1L //var n3 : Int = n2 //错误,原因不能把高精度的数据直接赋值和低精度。
//(byte, short) 和 char之间不会相互自动转换 var n4 : Byte = 1 //var c1 : Char = n4 错
//byte,short,char 他们三者可以计算,在计算时首先转换为int类型
var n5 : Byte = 1 var c2 : Char = 1 // var n : Short = n5 + c2 //当n5 + c2 结果类型就是int // var n6 : Short = 10 + 90 //错误 var n7 : Short = 100 //√
} }
|
2.12.2 高级隐式转换和隐式函数
- scala还提供了非常强大的隐式转换机制(隐式函数,隐式类等等),我们放在高级部分专门用一个章节来讲解
2.12.3 强制类型转换
- 介绍
自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。
- 案例演示
java : int num = (int)2.5
scala : var num : Int = 2.7.toInt
- 强制类型转换细节说明
1) 当进行数据的 从 大——>小,就需要使用到强制转换
2) 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
3) Char类型可以保存 Int的常量值,但不能保存Int的变量值,需要强转
4) Byte和Short类型在进行运算时,当做Int类型处理。
2.13 值类型转换-课堂练习题
2.14 值类型和String类型的转换
2.14.1 介绍
在程序开发中,我们经常需要将基本数据类型转成String 类型。或者将String类型转成基本数据类型。
2.14.2 基本类型转String类型
语法: 将基本类型的值+"" 即可
案例演示
2.14.3 String类型转基本数据类型
案例说明:
2.14.4 注意事项
1) 在将String 类型转成 基本数据类型时,要确保String类型能够转成有效的数据,比如 我们可以把 "123" , 转成一个整数,但是不能把 "hello" 转成一个整数
2) 思考就是要把 "12.5" 转成 Int ? // 错误,在转换中,不会使用截取
2.15 标识符的命名规范
2.15.1 标识符概念
Scala 对各种变量、方法、函数等命名时使用的字符序列称为标识符
凡是自己可以起名字的地方都叫标识符
2.15.2 标识符的命名规则(记住)
1) Scala中的标识符声明,基本和Java是一致的,但是细节上会有所变化。
2) 首字符为字母,后续字符任意字母和数字,美元符号,可后接下划线_
3) 数字不可以开头。
4) 首字符为操作符(比如+ - * / ),后续字符也需跟操作符 ,至少一个(反编译)
5) 操作符(比如+-*/)不能在标识符中间和最后.
6) 用反引号`....`包括的任意字符串,即使是关键字(39个)也可以 [true]
案例:
2.15.3 标识符举例说明
hello // ok
hello12 // ok
1hello // error
h-b // error
x h // error
h_4 // ok
_ab // ok
Int // ok , 因为在scala Int 是预定义的字符
Float // ok
_ // error ,因为在scala _ 有很多其他的作用。
Abc // ok
+*- // ok
+a // error
2.15.4 标识符命名注意事项
1) 包名:尽量采取有意义的包名,简短,有意义
2) 变量名、函数名 、方法名 采用驼峰法。
2.15.5 关键字介绍
Scala有39个关键字:
package, import, class, object, trait, extends, with, type, forSome
private, protected, abstract, sealed, final, implicit, lazy, override
try, catch, finally, throw
if, else, match, case, do, while, for, return, yield
def, val, var
this, super
new
true, false, null
第 3 章 运算符
3.1 运算符介绍
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。
1) 算术运算符
2) 赋值运算符
3) 比较运算符(关系运算符)
4) 逻辑运算符
5) 位运算符
3.2 算术运算符一览
3.2.1 案例演示
案例演示算术运算符的使用(Operator.scala)。
+, - , * , / , % 重点讲解 /、%
+、-、* 是一个道理,完全可以类推。
算数运算符的运算规则和Java一样
案例:
3.2.2 细节说明
1) 对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。 例如:var x : Int = 10/3 ,结果是 3
2) 当对一个数取模时,可以等价 a%b=a-a/b*b , 这样我们可以看到 取模的一个本质运算(和java 的取模规则一样)。
3) 注意:Scala中没有++、--操作符,需要通过+=、-=来实现同样的效果
4) 案例
3.2.3 课堂练习
假如还有97天放假,问:xx个星期零xx天
定义一个变量保存华氏温度,华氏温度转换摄氏温度的公式为:5/9*(华氏温度-100),请求出华氏温度对应的摄氏温度。[测试:232.5]
3.3 关系运算符(比较运算符)
3.3.1 一览图:
3.3.2 案例演示
3.3.3 细节说明
1) 关系运算符的结果都是Boolean型,也就是要么是true,要么是false。
2) 关系运算符组成的表达式,我们称为关系表达式。 a > b
3) 比较运算符“==”不能误写成“=”
4) 使用陷阱: 如果两个浮点数进行比较,应当保证数据类型一致.
3.4 逻辑运算符
3.4.1 一览图
3.5 赋值运算符
3.5.1 位运算符
3.6 运算符的特别说明
Scala不支持三目运算符 , 在Scala 中使用 if – else 的方式实现。
课堂练习
1) 案例1:求两个数的最大值
2) 案例2:求三个数的最大值
3.7 运算符优先级
3.8 键盘输入语句
3.8.1 介绍
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。InputDemo.scala
3.8.2 步骤 :
导入该类的所在包
创建该类对象(声明变量)
调用里面的功能
3.8.3 案例演示:
要求:可以从控制台接收用户信息,【姓名,年龄,薪水】。
1) 回顾Java的实现
2) Scala的实现
使用java模拟一下为什么可以通过object的名字,直接调用其方法.
第 4 章 流程控制
4.1 分支控制if-else
4.1.1 分支控制if-else介绍
让程序有选择的的执行,分支控制有三种:
单分支
双分支
多分支
4.1.2 单分支
- 基本语法
if (条件表达式) {
执行代码块
}
说明:当条件表达式为ture 时,就会执行 { } 的代码。
- 案例说明
请大家看个案例[IfDemo.scala]:
编写一个程序,可以输入人的年龄,如果该同志的年龄大于18岁,则输出 “age > 18”
4.1.3 单分支对应流程图
4.1.4 双分支
- 基本语法
if (条件表达式) {
执行代码块1
} else {
执行代码块2
}
- 案例演示
请大家看个案例[IfDemo2.scala]:
编写一个程序,可以输入人的年龄,如果该同志的年龄大于18岁,则输出 “age >18”。否则 ,输出 “ age <= 18 ”, 自己写。
- 双分支的流程图
- 应用案例:
练习题:
4.1.5 多分支
- 基本语法
if (条件表达式1) {
执行代码块1
}
else if (条件表达式2) {
执行代码块2
}
……
else {
执行代码块n
}
- 多分支的流程图
- 案例演示
请大家看个案例[IfDemo3.scala]:
岳小鹏参加scala考试,他和父亲岳不群达成承诺:
如果:
成绩为100分时,奖励一辆BMW;
成绩为(80,99]时,奖励一台iphone7plus;
当成绩为[60,80]时,奖励一个 iPad;
其它时,什么奖励也没有。
说明: 成绩在程序中指定!
代码如下:
- 案例演示2
求ax2+bx+c=0方程的根。a,b,c分别为函数的参数,如果:b2-4ac>0,则有两个解;
b2-4ac=0,则有一个解;b2-4ac<0,则无解; [a=3 b=100 c=6]
提示1:x1=(-b+sqrt(b2-4ac))/2a
X2=(-b-sqrt(b2-4ac))/2a
提示2:sqrt(num) 在 scala 包中(默认引入的) 的math 的包对象有很多方法直接可用.
//分析1. 多分支 2. 2. b2-4ac 计算出保存到一个变量 3. 引入math.sqrt
//走代码。
4.1.6 分支控制if-else 注意事项
1) 如果大括号{}内的逻辑代码只有一行,大括号可以省略, 这点和java 的规定一样。
2) Scala中任意表达式都是有返回值的,也就意味着if else表达式其实是有返回结果的,具体返回结果的值取决于满足条件的代码体的最后一行内容.[案例演示]
3) Scala中是没有三元运算符,因为可以这样简写
4) 案例演示
4.2 嵌套分支
4.2.1 基本介绍
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层分支外面的分支结构称为外层分支。嵌套分支不要超过3层
4.2.2 基本语法
if(){
if(){
}else{
}
}
4.2.3 应用案例1
参加百米运动会,如果用时8秒以内进入决赛,否则提示淘汰。并且根据性别提示进入男子组或女子组。【可以让学员先练习下5min】, 输入成绩和性别,进行判断。1分钟思考思路
double second; char gender;
4.2.4 多分支的案例应用案例
4.3 switch分支结构
在scala中没有switch,而是使用模式匹配来处理。
模式匹配涉及到的知识点较为综合,因此我们放在后面讲解
4.4 for循环控制
4.4.1 基本介绍
Scala 也为for 循环这一常见的控制结构提供了非常多的特性,这些for 循环的特性被称为for 推导式(for comprehension)或for 表达式(for expression)
4.4.2 范围数据循环方式1
- 基本案例
for(i <- 1 to 3){
print(i + " ")
}
println()
- 说明
1) i 表示循环的变量, <- 规定好 to 规定
2) i 将会从 1-3 循环, 前后闭合
3) 输出10句 "hello,SGG!"
4.4.3 范围数据循环方式2
- 基本案例
for(i <- 1 until 3) {
print(i + " ")
}
println()
说明:
1) 这种方式和前面的区别在于 i 是从1 到 3-1
2) 即使前闭合后开的范围
3) 输出10句 "hello,SGG!"
for ( int i = 0; i < arr.lenght(); i++) {
}
4.4.4 循环守卫
- 基本案例
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
println()
- 基本案例说明
1) 循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为true则进入循环体内部,为false则跳过,类似于continue
2) 上面的代码等价
for (i <- 1 to 3){
if (i != 2) {
print(i + "")
}
}
- 案例
4.4.5 引入变量
- 基本案例
for(i <- 1 to 3; j = 4 - i) {
print(j + " ")
}
- 对基本案例说明
1) 没有关键字,所以范围后一定要加;来隔断逻辑
2) 上面的代码等价
4.4.6 嵌套循环
- 基本案例
for(i <- 1 to 3; j <- 1 to 3) {
println(" i =" + i + " j = " + j)
}
- 对基本案例说明
1) 没有关键字,所以范围后一定要加;来隔断逻辑
2) 上面的代码等价
3) 案例
4.4.7 循环返回值
- 基本案例
val res = for(i <- 1 to 10) yield i
println(res)
- 对基本案例说明
将遍历过程中处理的结果返回到一个新Vector集合中,使用yield关键字
Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
4.4.8 使用花括号{}代替小括号()
- 基本案例
for(i <- 1 to 3; j = i * 2) {
println(" i= " + i + " j= " + j)
}
- 可以写成
for{
i <- 1 to 3
j = i * 2} {
println(" i= " + i + " j= " + j)
}
- 对基本案例说明
1) {}和()对于for表达式来说都可以
2) for 推导式有一个不成文的约定:当for 推导式仅包含单一表达式时使用圆括号,当其包含多个表达式时使用大括号
3) 当使用{} 来换行写表达式时,分号就不用写了
4) 案例
4.4.9 注意事项和细节说明
1) scala 的for循环形式和java是较大差异,这点请同学们注意,但是基本的原理还是一样的。
2) scala 的for循环是表达式,是有返回值的。java的for循环不是表达式,没有返回值.
3) scala 的for循环的步长如何控制! [for(i <- Range(1,3,2)]
4.4.10 for循环练习题(学员先做)
1) 打印1~100之间所有是9的倍数的整数的个数及总和.
2) 完成下面的表达式输出
4.5 while循环控制
4.5.1 基本语法
循环变量初始化
while (循环条件) {
循环体(语句)
循环变量迭代
}
4.5.2 while循环应用实例
画出流程图
输出10句"你好,SGG"
4.5.3 注意事项和细节说明
1) 循环条件是返回一个布尔值的表达式
2) while循环是先判断再执行语句
3) 与If语句不同,While语句本身没有值,即整个While语句的结果是Unit类型的()
4) 因为while中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量 ,而变量需要声明在while循环的外部,那么就等同于循环的内部对外部的变量造成了影响, 也就违背了函数式编程的重要思想(纯函数思想),所以不推荐使用,而是推荐使用for循环。
4.6 do..while循环控制
4.6.1 注意事项和细节说明
1) 循环条件是返回一个布尔值的表达式
2) do..while循环是先执行,再判断
3) 和while 一样,因为do…while中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量 ,而变量需要声明在do...while循环的外部,那么就等同于循环的内部对外部的变量造成了影响, 也就违背了函数式编程的重要思想(纯函数思想),所以不推荐使用,而是推荐使用for循环
4.6.2 课堂练习题【学员先做】
1) 计算1—100的和 【课后】
2) 统计1——200之间能被5整除但不能被3整除的个数 【课堂】
4.7 多重循环控制
4.7.1 介绍:
1) 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过3层】
2) 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。
3) 设外层循环次数为m次,内层为n次, 则内层循环体实际上需要执行m*n=mn次。
4.7.2 应用实例:
1) 统计三个班成绩情况,每个班有5名同学,求出各个班的平均分和所有班级的平均分[学生的成绩从键盘输入]。
package com.atguigu.chapter03.opertest.exer import scala.io._ object Mulforexam { def main(args: Array[String]): Unit = { /* 统计三个班成绩情况,每个班有5名同学, 求出各个班的平均分和所有班级的平均分[学生的成绩从键盘输入]。 分析: 1. 班级个数 classNum, 学生个数 sutNum, 总分 totalFen, 每个班级的总分 sum 2. 使用双层循环 */ val classNum = 3 val sutNum = 5 var totalFen = 0.0 var fen = 0.0 var sum = 0.0 for (i <- 1 to classNum) { sum = 0.0 //每个班级的总分 for (j <- 1 to sutNum) { printf("请输入第%d个班的第%d学生的成绩\n", i, j) fen = StdIn.readDouble() sum += fen } //sum 就是i班级总分 totalFen += sum printf("第%d个班级的平均分是%.2f \n", i, (sum / sutNum))
} printf("所有学生的平均分是%.2f \n", totalFen / (sutNum * classNum))
}
}
|
2) 统计三个班及格人数,每个班有5名同学。
在第一个案例的基础上调整即可:
3) 打印出九九乘法表
4.8 while循环的中断
4.8.1 基本说明
Scala内置控制结构特地去掉了break和continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和contine的功能,而不是一个关键字。
4.8.2 应用案例
对代码的总结
1) 在break() 函数 代码是
def break(): Nothing = { throw breakException }
2) breakable(op: =>Unit) 函数, 是一个高阶函数,可以接受另外一个函数或者一段并执行,并处理如果捕获到breakException, 就 throw ex
def breakable(op: => Unit) {
try {
op
} catch {
case ex: BreakControl =>
if (ex ne breakException) throw ex
}
}
3) 从这里看到,我们的while循环就是传入的代码,可以被breakable执行。
4) scala中有这个风格,如果我们传入的是参数【代码】,是多行的,则我们的可以使用{} 替代()
5) 在scala如果要使用continue的逻辑,则使用if-else 来处理。
6) 在for循环中,一样可以使用breakable( break() )
4.8.3 课堂练习题:
1) 100以内的数求和,求出 当和 第一次大于20的当前数【for】
2) 实现登录验证,有三次机会,如果用户名为”张无忌” ,密码”888”提示登录成功,否则提示还有几次机会,请使用for 循环完成
第 5 章 函数式编程基础
5.1 函数式编程内容及授课顺序说明
5.2 函数式编程授课顺序即oop和函数式编程关系
5.2.1 几个概念的说明
在学习Scala中将方法、函数、函数式编程和面向对象编程明确一下:
1) 在scala中,方法和函数几乎可以等同(比如他们的定义、使用、运行机制都一样的),只是函数的使用方式更加的灵活多样。
2) 函数式编程是从编程方式(范式)的角度来谈的,可以这样理解函数式编程把函数当做一等公民,充分利用函数 支持的函数的多种使用方式。
比如:
在Scala当中,函数是一等公民,像变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量. ,函数的创建不用依赖于类或者对象,而在Java当中,函数的创建则要依赖于类、抽象类或者接口.
3) 面向对象编程是以对象为基础的编程方式。
4) 在scala中函数式编程和面向对象编程融合在一起了。
5.3 函数式编程介绍
- "函数式编程"是一种"编程范式"(programming paradigm)。
- 它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
- 函数式编程中,将函数也当做数据类型,因此可以接受函数当作输入(参数)和输出(返回值)。
- 函数式编程中,最重要的就是函数。
5.4 为什么需要函数
请大家完成这样一个需求:
输入两个数,再输入一个运算符(+,-,*,/),得到结果.。
先使用传统的方式来解决,看看有什么问题没有?
1) 代码冗余
2) 不利于代码的维护
5.5 函数介绍
为完成某一功能的程序指令(语句)的集合,称为函数
5.5.1 基本语法
5.5.2 函数的快速入门
5.6 函数-调用机制
5.6.1 函数-调用过程
- 为了让大家更好的理解函数调用过程, 看1个案例,并画出示意图,这个很重要
- 计算两个数的和,并返回 getSum
5.7 函数-递归调用
5.7.1 基本介绍
一个函数在函数体内又调用了本身,我们称为递归调用
5.7.2 递归调用快速入门
5.7.3 函数递归需要遵守的重要原则:
1) 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2) 函数的局部变量是独立的,不会相互影响
3) 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)
4) 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数栈本身也会被系统销毁
5.7.4 递归课堂练习题
- 题1:斐波那契数 [学员练习]
请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13...
给你一个整数n,求出它的斐波那契数是多少?
- 题2:求函数值 [演示]
已知 f(1)=3; f(n) = 2*f(n-1)+1;
请使用递归的思想编程,求出 f(n)的值?
- 题3:猴子吃桃子问题
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子?
5.8 函数注意事项和细节讨论
1) 函数的形参列表可以是多个,函数没有形参,调用时 可以不带()
2) 形参列表和返回值列表的数据类型可以是值类型和引用类型。【案例演示】
3) Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下,return关键字可以省略 [前面我们讲快速入门说过]
4) 因为Scala可以自行推断,所以在省略return关键字的场合,返回值类型也可以省略
5) 如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然如果你什么都不写,即使有return 返回值为()
6) 如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值
7) 如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为Any)
8) Scala语法中任何的语法结构都可以嵌套其他语法结构,即:函数中可以再声明/定义函数,类中可以再声明类 ,方法中可以再声明/定义方法
9) Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值
10) 如果函数存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数 [案例演示+练习]
11) scala 函数的形参默认是val的,因此不能在函数中进行修改.
12) 递归函数未执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型
13) Scala函数支持可变参数
//支持0到多个参数
def sum(args :Int*) : Int = {
}
//支持1到多个参数
def sum(n1: Int, args: Int*) : Int = {
}
说明:
1) args 是集合, 通过 for循环 可以访问到各个值。
2) 案例演示: 编写一个函数sum ,可以求出 1到多个int的和
5.9 函数练习题
5.10 过程
5.10.1 基本介绍
将函数的返回类型为Unit的函数称之为过程(procedure),如果明确函数没有返回值,那么等号可以省略
5.10.2 案例说明:
5.11 惰性函数
5.11.1 看一个应用场景
惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。首先,您可以将耗时的计算推迟到绝对需要的时候。其次,您可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。Java 并没有为惰性提供原生支持,Scala提供了。
5.11.2 画图说明[大数据推荐系统]
5.11.3 Java实现懒加载的代码
5.11.4 介绍
当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数,在Java的某些框架代码中称之为懒加载(延迟加载)。
5.11.5 案例演示
看老师演示+反编译
5.11.6 注意事项和细节
1) lazy 不能修饰 var 类型的变量
2) 不但是 在调用函数时,加了 lazy ,会导致函数的执行被推迟,我们在声明一个变量时,如果给声明了 lazy ,那么变量值得分配也会推迟。 比如 lazy val i = 10
5.12 异常
5.12.1 介绍
1) Scala提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有任意数量的try...catch块。
2) 语法处理上和Java类似,但是又不尽相同
5.12.2 Java异常处理回顾
5.12.3 Java异常处理的注意点.
1) java语言按照try—catch—finally的方式来处理异常
2) 不管有没有异常捕获,都会执行finally, 因此通常可以在finally代码块中释放资源
3) 可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示 "Exception 'java.lang.xxxxxx' has already been caught"【案例演示】
5.12.4 Scala异常处理举例
5.12.5 scala异常的小结和注意事项
1) 我们将可疑代码封装在try块中。 在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止。
2) Scala的异常的工作机制和Java一样,但是Scala没有“checked(编译期)”异常,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。
3) 用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方
4) 在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case子句来匹配异常。【前面案例可以看出这个特点, 模式匹配我们后面详解】,当匹配上后 => 有多条语句可以换行写,类似 java 的 switch case x: 代码块..
5) 异常捕捉的机制与其他语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在scala中也不会报错,但这样是非常不好的编程风格。
6) finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样。
7) Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息。 它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在scala中,可以使用throws注释来声明异常
5.13 函数的课堂练习题
编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表
第 6 章 面向对象编程基础部分
6.1 类与对象
6.1.1 看一个养猫猫问题
张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。
猫有三种数据(属性) è 使用变量不好用
需要的数据类型
1.存储不同数据类型数据。
2.可以对这些数据进行操作
3.类
6.1.2 快速入门-面向对象的方式解决养猫问题
6.1.3 如何定义类
- 基本语法
[修饰符] class 类名 {
类体
}
- 定义类的注意事项
1) scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public),[修饰符在后面再详解].
2) 一个Scala源文件可以包含多个类.
6.1.4 属性
- 基本介绍
- 案例演示:
属性是类的一个组成部分,一般是值数据类型,也可是引用类型。比如我们前面定义猫类 的 age 就是属性
- 属性的细节和注意事项
案例代码:
package com.atguigu.chapter06.Demo
object Test01 { def main(args: Array[String]): Unit = { //创建一个Cat对象 var cat : Cat = new Cat() var cat2 = new Cat() // 这里的.name 并不是直接访问属性,而是通过一个public方法访问.(讲解属性时,再剖析) cat2.name = "tom猫" //public void name_$eq(String x$1) { this.name = x$1; } cat2.age = 3 cat2.color = "黑色" println("cat2.name=" + cat2.name)// public String name() var tiger = new Tiger() cat2.boss = tiger println("cat2.boss.name=" + cat2.boss.name) //cat2.boss().name() } } class Cat { //如果这里是class 类 ,在底层只有一个类Cat //三个属性 var name: String = "" // 在底层,会生成类似java的 setter [xx_$eq] getter [xxxx()] var age: Int = 0 var color: String = "" var boss: Tiger = _ // _ 表示给属性一个默认值
}
class Tiger{ // 对应一个 Tiger.class var name = "华南虎~" }
|
6.1.5 属性的高级部分
6.1.6 如何创建对象
- 基本语法
val | var 对象名 [:类型] = new 类型()
- 说明
1) 如果我们不希望改变对象的引用(即:内存地址), 应该声明为val 性质的,否则声明为var, scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用
2) scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,但当类型和后面new 对象类型有继承关系即多态时,就必须写了
代码如下:
package com.atguigu.chapter06.ClassAndObject
object Demo01 { def main(args: Array[String]): Unit = { //在开发中,我们会从数据库中获取对个对象(封装) //我们通常不会去改变e对象本身 e = null //通常是对e对象进行查看,或者修改 val e = new Employee() e.sal += 1.0 //e = null 在开发中不多,因此我们创建对象时,一般来说是val println("e.sal=" + e.sal)
/* scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略, 但当类型和后面new 对象类型有继承关系即多态时,就必须写了 */ val e2: A = new Employee() println(e2) // e2 } }
class A { } //Employee类 class Employee extends A{ var id: Int = 10 var name: String = "tom" var sal: Double = 10000.0
override def toString: String = { return "id=" + id + "name=" + name + "sal=" + sal } }
|
6.1.7 类和对象的内存分配机制
- 看一个思考题
我们定义一个Person类(包括 名字,年龄) ,创建一个对象(ObjectDemo.scala) - 写了一段代码,并画出了示意图:
- 代码对应的内存布局示意图
6.1.8 方法
- 基本说明
Scala中的方法其实就是函数,只不过一般将对象中的函数称之为方法。声明规则请参考函数式编程中的函数声明。
- 基本语法,和函数一样
def 方法名(参数列表) [:返回值类型] = {
方法体
}
- 方法案例演示
给Cat类添加cal方法,可以计算两个数的和
6.1.9 课堂练习题
1) 编写类(MethodExec),编程一个方法,方法不需要参数,在方法中打印一个
10*8 的矩形,在main方法中调用该方法。
2) 修改上一个程序,编写一个方法中,方法不需要参数,计算该矩形的面积,并将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印(结果保留小数点2位)。
第一题和第二题的代码:
课后的练习
6.2 类与对象应用实例
6.2.1 小狗案例
1) 编写一个Dog类,包含name(String)、age(int)、weight(double)属性
2) 类中声明一个say方法,返回String类型,方法返回信息中包含所有属性值。
3) 在另一个TestDog Object中的main方法中,创建Dog对象,并访问say方法和所有属性,将调用结果打印输出
4) 代码:
6.3 构造器
6.3.1 看一个需求
我们来看一个需求:前面我们在创建Person的对象时,是先把一个对象创建好后,再给他的年龄和姓名属性赋值,如果现在我要求,在创建人类的对象时,就直接指定这个对象的年龄和姓名,该怎么做? 这时就可以使用构造方法/构造器。
6.3.2 回顾-Java构造器基本语法
[修饰符] 方法名(参数列表){
构造方法体
}
6.3.3 回顾-Java构造器的特点
1) 在Java中一个类可以定义多个不同的构造方法,构造方法重载
2) 如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫默认构造器),比如 Person (){}
3) 一旦定义了自己的构造方法,默认的构造方法就覆盖了,就不能再使用默认的无参构造方法,除非显示的定义一下,即: Person(){};
4) 案例
6.3.4 Scala构造器的介绍
和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala类的构造器包括: 主构造器 和 辅助构造器
6.3.5 Scala构造器的基本语法
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
。
6.3.6 快速入门
创建Person对象的同时初始化对象的age属性值和name属性值
6.3.7 Scala构造器注意事项和细节
1) Scala构造器作用是完成对新对象的初始化,构造器没有返回值。
2) 主构造器的声明直接放置于类名之后 [反编译]
3) 主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别【案例演示+反编译]
对应的反编译的文件
4) 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
5) 辅助构造器名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分, 在底层就是java的构造器重载.
在对应的反编译的.class 对应class Person2
6) 如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了.
说明:因为Person3的主构造器是私有,因此就需要使用辅助构造器来创建对象。
7) 辅助构造器的声明不能和主构造器的声明一致,会发生错误
6.4 属性高级
前面我们讲过属性了,这里我们再对属性的内容做一个加强.
6.4.1 构造器参数
1) Scala类的主构造器函数的形参未用任何修饰符修饰,那么这个参数是局部变量。
2) 如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性使用 【案例+反编译】
3) 如果参数使用var关键字声明,那么那么Scala会将参数作为类的成员属性使用,并会提供属性对应的xxx()[类似getter]/xxx_$eq()[类似setter]方法,即这时的成员属性是私有的,但是可读写。【案例+反编译】
4) 代码
对应的反编译的.class文件
6.4.2 Bean属性
- 基本说明
JavaBeans规范定义了Java的属性是像getXxx()和setXxx()的方法。许多Java工具(框架)都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性。
- 注意
给某个属性加入@BeanPropetry注解后,会生成getXXX和setXXX的方法
并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以共存。
- 案例演示
反编译的.class文件
6.5 对象创建的流程分析
6.5.1 看一个案例
class Person {
var age: Int = 90
var name: String = _
def this(n: String, a: Int) {
this()
this.name = n
this.age = a
}
}
var p : Person = new Person("小倩",20)
6.5.2 流程分析(面试题)
1) 加载类信息(属性信息,方法信息)
2) 在堆中,给对象开辟空间
3) 调用主构造器对属性进行初始化
4) 使用辅助构造器对属性进行初始化
5) 把对象空间的地址,返回给p引用
第 7 章 面向对象编程(中级)
7.1 包
7.1.1 看一个应用场景
现在有两个程序员共同开发一个项目,程序员xiaoming希望定义一个类取名 Dog ,程序员xiaoqiang也想定义一个类也叫 Dog。两个程序员为此还吵了起来,怎么办?
7.1.2 回顾-Java包的三大作用
1) 区分相同名字的类
2) 当类很多时,可以很好的管理类
3) 控制访问范围
7.1.3 回顾-Java打包命令
打包基本语法
package com.atguigu;
7.1.4 打包的本质分析
实际上就是创建不同的文件夹来保存类文件,画出示意图。
7.1.5 快速入门
使用打包技术来解决上面的问题,不同包下Dog类
7.1.6 回顾-Java如何引入包
语法: import 包;
比如 import java.awt.*;
我们引入一个包的主要目的是要使用该包下的类
比如 import java.util.Scanner; 就只是引入一个类Scanner。
7.1.7 回顾-Java包的特点
java中包名和源码所在的系统文件目录结构要一致,并
且编译后的字节码文件路径也和包名保持一致。[如图]
7.1.8 Scala包的基本介绍
和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些,下面我们学习Scala包的使用和注意事项。
7.1.9 Scala包快速入门
使用打包技术来解决上面的问题,不同包下Dog类
7.1.10 Scala包的特点概述
- 基本语法
package 包名
- Scala包的三大作用(和Java一样)
1) 区分相同名字的类
2) 当类很多时,可以很好的管理类
3) 控制访问范围
- Scala中包名和源码所在的系统文件目录结构要可以不一致,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)。[案例演示]
- 案例
7.1.11 Scala包的命名
- 命名规则:
只能包含数字、字母、下划线、小圆点.,但不能用数字开头, 也不要使用关键字。
demo.class.exec1 //对吗 错误,因为 class 关键字
demo.12a // 对吗,错误
- 命名规范:
一般是小写字母+小圆点一般是
com.公司名.项目名.业务模块名
比如:com.atguigu.oa.model com.atguigu.oa.controller
com.sina.edu.user
com.sohu.bank.order
7.1.12 Scala会自动引入的常用包
7.1.13 Scala包注意事项和使用细节
1) scala进行package 打包时,可以有如下形式
2) 包也可以像嵌套类那样嵌套使用(包中有包), 这个在前面的第三种打包方式已经讲过了,在使用第三种方式时的好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,这样就非常灵活了。[案例+反编译]
3) 作用域原则:可以直接向上访问。即: Scala中子包中直接访问父包中的内容, 大括号体现作用域。(提示:Java中子包使用父包的类,需要import)。在子包和父包 类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可
package com.atguigu { class A01 { //这时,编译器,就会在com.atguigu包下生成A01.class
} class Cat{ //这时,编译器,就会在com.atguigu包下生成Cat.class
} package scala{ class A01 { ////这时,编译器,就会在com.atguigu.sacla包下生成A01.class
} class Cat {////这时,编译器,就会在com.atguigu.sacla包下生成Cat.class
} object Test01{ //这时,编译器,就会在com.atguigu.sacla包下生成Test01.class Test01$.class def main(args: Array[String]): Unit = { println("ok~~")
//创建一个Cat对象 //说明 //1. Scala中子包中直接访问父包中的内容 //2. 在子包和父包 类重名时,默认采用就近原则 //3. 如果希望指定使用某个类,则带上包全名即可 val cat = new Cat() println("cat=" + cat) //使用的是com.atguigu.scala.Cat[使用就近原则] val cat2 = new com.atguigu.Cat()//使用的是com.atguigu.Cat[指定原则] println("cat2" + cat2)
} } } } |
4) 父包要访问子包的内容时,需要import对应的类等
5) 可以在同一个.scala文件中,声明多个并列的package(建议嵌套的pakage不要超过3层)
6) 包名可以相对也可以绝对,比如,访问BeanProperty的绝对路径是:
_root_. scala.beans.BeanProperty ,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理
7.1.14 包对象
基本介绍:包可以包含类、对象和特质trait,但不能包含函数或变量的定义。这是Java虚拟机的局限。为了弥补这一点不足,scala提供了包对象的概念来解决这个问题
7.1.15 包对象的应用案例
7.1.16 包对象的注意事项
7.2 包的可见性
7.2.1 Scala中包的可见性介绍:
在Java中,访问权限分为: public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。
7.2.2 Scala中包的可见性和访问修饰符的使用
1) 当属性访问权限为默认时,从底层看属性是private的,但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter] 方法,因此从使用效果看是任何地方都可以访问)
2) 当方法访问权限为默认时,默认为public访问权限
3) private为私有权限,只在类的内部和伴生对象中可用 【案例演示】
4) protected为受保护权限,scala中受保护权限比Java中更严格,只能子类访问,同包无法访问
5) 在scala中没有public关键字,即不能用public显式的修饰属性和方法。【案演】
6) 包访问权限(表示属性有了限制。同时增加了包的访问权限),这点和Java不一样,体现出Scala包使用的灵活性。
7.3 包的引入
7.3.1 Scala引入包的细节和注意事项
1) 在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范围,提高效率。
2) Java中如果想要导入包中所有的类,可以通过通配符*,Scala中采用下 _ [案例演示]
3) 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)
4) 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名
5) 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉
import java.util.{ HashMap=>_, _} //
7.4 面向对象编程方法-抽象
7.4.1 如何理解抽象
我们在前面去定义一个类时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。[见后面ppt]
- 代码如下
package com.atguigu.chapter06.abstractt
object AccountDemo01 { def main(args: Array[String]): Unit = { //测试一下我们抽象的Account是否ok val myAccount = new Account("gh111111",200.0,123456) //查询 myAccount.query(123456) //取款 myAccount.withdraw(123456, 201) myAccount.query(123456)
} }
/* 不管是哪种账号, 是谁的账号 属性: 账号,余额,密码 方法 存款(depostie) 取款(withdraw) 查询(query)
*/ class Account(InAccountNO:String,money:Double,inPwd:Int) { val accountNo:String = InAccountNO var banlance:Double = money var pwd:Int = inPwd
//查询 def query(pwd:Int): Unit = { if (pwd != this.pwd) { println("密码错误") return } println(this.accountNo + " 余额是:" + this.banlance) }
//withdraw取款 def withdraw(pwd:Int, money:Int): Unit = { //判断 if (pwd != this.pwd) { println("密码错误") return } if (money > this.banlance) { println("余额不足") return } this.banlance -= money println("取款成功" + this.accountNo + " 取出来 " + money + " 余额有:" + this.banlance) }
}
|
7.5 面向对象编程三大特征
7.5.1 基本介绍
面向对象编程有三大特征:封装、继承和多态。下面我们一一为同学们进行详细的讲解。
7.5.2 封装介绍
封装(encapsulation)就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作
7.5.3 封装的理解和好处
1) 隐藏实现细节
2) 提可以对数据进行验证,保证安全合理
7.5.4 如何体现封装
1) 对类中的属性进行封装
2) 通过成员方法,包实现封装
7.5.5 封装的实现步骤
1) 将属性进行私有化
2) 提供一个公共的set方法,用于对属性判断并赋值
def setXxx(参数名 : 类型) : Unit = {
//加入数据验证的业务逻辑
属性 = 参数名
}
3) 提供一个公共的get方法,用于获取属性的值
def getXxx() [: 返回类型] = {
return 属性
}
7.5.6 快速入门案例
- 看一个案例
那么在Scala中如何实现这种类似的控制呢?
请大家看一个小程序(TestEncap.scala),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证[要求1-120之间]。
7.5.7 封装的课堂练习题
7.5.8 Scala封装的注意事项和细节
7.6 面向对象编程-继承
7.6.1 Java继承的简单回顾
class 子类名 extends 父类名 { 类体 }
子类继承父类的属性和方法
7.6.2 继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(比如Student),在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends语句来声明继承父类即可。
和Java一样,Scala也支持类的单继承。
画出继承的示意图
7.6.3 Scala继承的基本语法
class 子类名 extends 父类名 { 类体 }
7.6.4 Scala继承快速入门
编写一个Student 继承 Person的案例,体验一下Scala继承的特点
package com.atguigu.chapter06.extend
object Extends01 { def main(args: Array[String]): Unit = { //测试 val stu1 = new Student stu1.name = "tom" stu1.age = 20 stu1.showInfo() stu1.studying()
} }
class Person { //属性name 和 age,默认 var name : String = _ var age : Int = _ //方法 def showInfo(): Unit = { println("学生信息如下:") println("名字:" + this.name) } }
//extends关键字,继承了Person class Student extends Person { //将Student特有方法写在Student类 def studying(): Unit = { println(this.name + "学习 scala中....") } } |
7.6.5 Scala继承给编程带来的便利
1) 代码的复用性提高了
2) 代码的扩展性和维护性提高了【面试官问?】
7.6.6 子类继承了什么,怎么继承了?
子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问【debug代码验证可以看到】
代码:
对应的反编译的.class 文件
7.6.7 重写方法
说明: scala明确规定,重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字 【案例演示+反编译】
- 案例演示
说明
1) 当上面的代码被反编译后,我们发现override 关键字也去掉,即和java是一样.
2) super在反编译后,和java也一样。
7.6.8 Scala中类型检查和转换
- 基本介绍
1) 要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。
2) classOf[String]就如同Java的 String.class 。
3) obj.isInstanceOf[T]就如同Java的obj instanceof T 判断obj是不是T类型。
4) obj.asInstanceOf[T]就如同Java的(T)obj 将obj强转成T类型。
- 案例演示
- 最佳实践
- 最佳实践的代码
package com.atguigu.chapter06.extend
object TypeConvertExer { def main(args: Array[String]): Unit = { //创建对象 val stu = new Stu val worker = new Worker test(stu) //stuid=.. test(worker)//workerId=
} def test(p: Person04): Unit = { //如果传入是Student实例,则调用sayOk //如果传入是Worker实例,则调用sayHello //进行p.asInstanceOf[T]转换时,要求,p 的引用本身就是指向T类型的引用 if (p.isInstanceOf[Stu]) { //转换 p.asInstanceOf[Stu].sayOk() } else if (p.isInstanceOf[Worker]) { p.asInstanceOf[Worker].sayHello() } else { println("转换错误") } } }
class Person04 { var name : String = "tom" def printName() { println("Person printName() " + name) } }
class Stu extends Person04 { var stuId:Int = 100 def sayOk(): Unit = { println("stuid=" + this.stuId) } }
class Worker extends Person04 { var workerId:Int = 200 def sayHello(): Unit = { println("workerId=" + this.workerId) } }
|
7.6.9 Scala中超类的构造
- 回顾-Java中超类的构造
说明:
从代码可以看出:在Java中,创建子类
对象时,子类的构造器总是去调用一个
父类的构造器(显式或者隐式调用)
代码:
package com.atguigu.chapter06.extend;
public class SuperConstruct { public static void main(String[] args) { //测试 //下面代码执行的效果是: // A() // B() // A(String name)jack // B(String name)jack B02 b = new B02(); B02 b2 = new B02("jack"); } } //父类 A02 class A02 { //构造器无参. public A02() { System.out.println("A()"); } //构造器,有参 public A02(String name) { System.out.println("A(String name)" + name); } } //子类B02,继承A02 class B02 extends A02{
public B02() { //这里会隐式调用super(); 就是无参的父类构造器A02() super(); System.out.println("B()"); } public B02(String name) { super(name); System.out.println("B(String name)" + name); } }
|
- Scala超类的构造说明
1) 类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用.),这点在前面我们说过了。
上面的代码说明
(1) 先去执行extends Person05() 的Person05()
(2) 在执行class Emp05 类中的 主构造器 Emp05()
(3) 再执行 Emp05的辅助构造器 this(name:String)
2) 只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在Scala的构造器中,你不能调用super(params).
- 应用实例
要求:编写程序,创建一个学生对象。体会Scala中超类的构造流程
package com.atguigu.chapter06.extend
object ScalaSuperConstructExer1 { def main(args: Array[String]): Unit = { //创建一个学生对象 //分析下面的代码是如何运行的 //1.Person06( pname : String ) name= jack //2.Student06( studentname : String ) sno = 20
val stu = new Student06("jack") stu.printName() //Student printName() name jack sno 20 //Person printName() jack } }
class Person06( pname : String ) { var name : String = pname def printName() { println("Person printName() " + name) } def this() { this("xx") } } class Emp06(empname : String) extends Person06(empname) { var empno : Int = 10 //这里需要显示的使用override override def printName() { println("Emp printName() " + name) super.printName() } }
class Student06( studentname : String ) extends Person06() { //xxx var sno : Int = 20 override def printName() { println("Student printName() " + name + "sno=" + sno) super.printName() } } |
- 对上面的案例小结
class Student06( studentname : String ) extends Person06()
1) 当一个子类继承了一个父类后,是上面语法.
2) extends Person06() 表示 当我们构建一个子类对象,会调用父类的构造器 Person06(),要求我们的父类,必须有一个对应的构造器,该构造器可以是主构造器,也可以是辅助构造器.
3) 父类的所有辅助构造器,仍然需要调用父类的主构造器
7.6.10 覆写字段
- 基本介绍
在Scala中,子类改写父类的字段,我们成为覆写/重写字段。覆写字段需使用 override修饰。
- 回顾:在Java中只有方法的重写,没有属性/字段的重写,准确的讲,是隐藏字段代替了重写。参考:Java中为什么字段不能被重写.doc [字段隐藏案例]。
- 回顾了java的动态绑定机制:
package com.atguigu.chapter06.extend;
public class DaynDemo { public static void main(String[] args) { AA a = new BB(); //提出java的动态绑定机制 //1. 当调用对象方法的时候,该方法会和该对象的内存地址绑定 //2. 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用 //说明: //1. 如果没有注销BB的sum, a.sum() = 40 //2. 如果注销BB的sum, a.sum() = 30 //3. 如果没有注销BB的sum1, a.sum1() = 30 //4. 如果注销BB的sum1, a.sum1() = 20 System.out.println("a.sum=" + a.sum()); //40 =》 30 System.out.println("a.sum1=" + a.sum1());//30 =》 20 } }
class AA { public int i = 10; public int sum() { return getI() + 10; } public int sum1() { return i + 10; } public int getI() { return i; } }
class BB extends AA { public int i = 20; // public int sum() { // return i + 20; // } public int getI() { return i; } // public int sum1() { // return i + 10; // } } |
- 对代码的说明:
//提出java的动态绑定机制
//1. 当调用对象方法的时候,该方法会和该对象的内存地址绑定
//2. 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用
- Scala覆写字段快速入门
我们看一个关于覆写字段的案例。【案例演示+分析+反编译】
- 覆写字段的注意事项和细节
1) def只能重写另一个def(即:方法只能重写另一个方法)
2) val只能重写另一个val 或 重写不带参数的def [案例+分析]
为什么编译器不支持 val 去重写 var 属性,原因是会造成数据设置和或者不一致.
b.age = 10 // b.age 调用的是父类的 public age_$eq()
println(b.age) //b.age 调用的子类的public age()
子类的val 属性,可以去重写 父类不带参数的def。
3) var只能重写另一个抽象的var属性
案例演示:
- 抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类
- var重写抽象的var属性小结
1) 一个属性没有初始化,那么这个属性就是抽象属
2) 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类
3) 如果是覆写一个父类的抽象属性,那么override 关键字可省略 [原因:父类的抽象属性,生成的是抽象方法,因此就不涉及到方法重写的概念,因此override可省略]
7.6.11 抽象类
- 基本介绍
在Scala中,通过abstract关键字标记不能被实例化的类。方法不用标记abstract,只要省掉方法体即可。抽象类可以拥有抽象字段,抽象字段就是没有初始值的字段
- 快速入门案例
我们看看如何把Animal做成抽象类, 包含一个抽象的方法cry()
- 抽象类基本语法
7.6.12 Scala抽象类使用的注意事项和细节讨论
1) 抽象类不能被实例
2) 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
3) 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract
4) 抽象方法不能有主体,不允许使用abstract修饰。
5) 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为abstract类。【案例演示+反编译】
6) 抽象方法和抽象属性不能使用private、final 来修饰,因为这些关键字都是和重写/实现相违背的。
7) 子类重写抽象方法不需要override,写上也不会错.
7.6.13 匿名子类
- 基本介绍
Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类
- 回顾-java匿名子类使用
- scala的匿名子类的创建
7.6.14 课后练习题
第 8 章
面向对象编程-高级
8.1 静态属性和方法
8.1.1 静态属性-提出问题
提出问题的主要目的就是让大家思考解决之道,从而引出我要讲的知识点.
说:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?请使用面向对象的思想,编写程序解决
基本介绍
8.1.2 回顾下Java的静态概念
public static 返回值类型 方法名(参数列表) {方法体}
说明: Java中静态方法并不是通过对象调用的,而是通过类对象调用的,所以静态操作并不是面向对象的。
8.1.3 Scala中静态的概念-伴生对象
说明:Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
8.1.4 伴生对象的快速入门
- 基本的案例代码
代码的说明
1) println(ScalaPerson.sex) //这里底层是 ScalaPerson$.Module$.sex()
2) 特点
class ScalaPerson { //生成 ScalaPerson类,会有对 object ScalaPerson 的属性或方法操作
var name : String = _
}
8.1.5 伴生对象的小结
8.1.6 伴生对象解决小孩游戏问题
如果,设计一个var total Int表示总人数,我们在创建一个小孩时,就把total加1,并且 total是所有对象共享的就ok了!,我们使用伴生对象来解决
画一个小图给大家理解。
package com.atguigu.chapter08.objectt
object ChildGame { def main(args: Array[String]): Unit = { val child1 = new Child("白骨精") val child2 = new Child("蜘蛛精") val child3 = new Child("犀牛精") Child.joinGame(child1) //Child$.Module$ 这个是一个静态变量 Child.joinGame(child2) Child.joinGame(child3) //显示人数 Child.showNum() } }
class Child(cName: String) { var name: String = cName }
object Child { //静态的属性 var totalNum: Int = 0 //加入游戏的方法 def joinGame(c: Child): Unit = { println(c.name + " 加入到游戏中...") totalNum += 1 } //显示当前有多少小孩的方法 def showNum(): Unit = { printf("当前有%d个小孩完游戏\n", totalNum) } }
|
8.1.7 apply方法的补充
8.2 单例对象
8.2.1 什么是单例对象
单例对象是指:使用单例设计模式保证在整个的软件系统中,某个类只能存在一个对象实例
8.2.2 回顾-Java单例对象
在Java中,创建单例对象分为饿汉式和懒汉式,我们看看是如何实现的
步骤如下:
1) 构造器私有化
2) 类的内部创建对象
3) 向外暴露一个静态的公共方法
4) 代码实现
package com.atguigu.chapter08.single;
public class JavaSingle { public static void main(String[] args) { //饿汉式 Single s1 = Single.getInstance();//得到一个Single对象 Single s2 = Single.getInstance();//得到一个Single对象 System.out.println(s1 == s2); // true
//懒汉式
//对上面两种方式的优化 System.out.println("----------------------------------"); Singleton ss1 = Singleton.getInstance(); Singleton ss2 = Singleton.getInstance(); System.out.println("ss1.hashcode" + ss1.hashCode()); System.out.println("ss2.hashcode" + ss2.hashCode());
} }
//缺点是 创建了,但是用,有资源浪费 //优点:线程安全。 class Single{// 饿汉式 private Single(){} //私有化构造器 private static Single s = new Single(); public static Single getInstance(){ return s; } }
//优点:在使用的时候,才去创建,因此没有资源的浪费。 //缺点:有线程安全问题 class Single2{// 懒汉式 private Single2(){} //私有化构造器 private static Single2 s = null; public static Single2 getInstance(){ if (s == null) { s = new Single2(); } return s; } }
//对上面的两种方法进行优化,使用静态内部类来优化 class Singleton { private Singleton() {} //私有化 // //静态内部类 1.在使用到时,才会加载 2.在加载时,不会中断(线程安全) private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { //对外提供的public静态方法 return SingletonInstance.INSTANCE; } } |
8.2.3 Scala中单例对象
8.2.4 单例模式的面试题
8.3 接口
8.3.1 回顾Java接口
- 声明接口
interface 接口名
- 实现接口
class 实现类类名 implements 接口名1,接口2
1) 在Java中, 一个类可以实现多个接口。
2) 在Java中,接口之间支持多继承
3) 接口中属性都是常量
4) 接口中的方法都是抽象的
8.3.2 Scala接口的介绍
从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口。
Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。
- 如何理解特质?图
8.4 特质(trait)
8.4.1 trait 的声明
trait 特质名 {
trait体
}
1) trait 命名 一般首字母大写.
Cloneable , Serializable
object T1 extends Serializable {
}
Serializable: 就是scala的一个特质。
8.4.2 Scala中trait 的使用
说明
1) 类和特质关系,使用的 继承的关系。因为scala的特质,有传统interface 特点,同时又抽象类特点。
2) 当一个类去继承特质时,第一个连接词是extends ,后面是with
3) 如果一个类在继承特质和父类时,应当把父类写在extends后。
8.4.3 特质的快速入门案例
- 快速入门的要求
- 具体代码
package com.atguigu.chapter08.traitpart
object TraitDemo01 { def main(args: Array[String]): Unit = { println("ok~") val c = new C c.getConnect("root", "123456") var f = new F f.getConnect("admin", "11111") } }
//增加获取数据库连接功能(实现可以不同) //(trait)
//编写一个trait ,写上连接数据库的方法的规范 trait Trait01 { //抽象方法 def getConnect(user:String, pwd:String): Unit }
//1. 先把继承关系写出
class A {} class B extends A{} class C extends A with Trait01{ override def getConnect(user: String, pwd: String): Unit = { println("连接mysql数据库...") } }
class D {} class E extends D {} class F extends D with Trait01{ override def getConnect(user: String, pwd: String): Unit = { println("连接oracle数据库..") } }
|
- 代码的说明
1) 如果我们创建了一个trait [Trait01] , 该trait只有抽象的方法,那么在底层就只会生成一个interface , 文件Trait01.class
2) 继承了trait的类,并行实现trait的抽象方法(这点和java一样)
8.4.4 特质trait 的再说明
1) Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。【案例演示+反编译】
2) 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质
3) 所有的java接口都可以当做Scala特质使用
8.4.5 带有特质的对象,动态混入
1) 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能【反编译看动态混入本质】
代码:
2) 此种方式也可以应用于对抽象类功能进行扩展
3) 动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。
4) 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能
5) 思考:如果抽象类中有没有实现的方法,如何动态混入特质?
8.4.6 scala创建对象的四种形式
8.4.7 叠加特质
- 基本介绍
1) 构建对象的同时如果混入多个特质,称之为叠加特质,
2) 那么特质声明顺序从左到右,方法执行顺序从右到左。
- 叠加特质应用案例
1) 目的:分析叠加特质时,对象的构建顺序,和执行方法的顺序
2) 代码
package com.atguigu.chapter08.traitpart
//叠加特质案例 object TraitDemo05 { def main(args: Array[String]): Unit = { val mySQL4 = new MySQL4 with File4 with DB4 //请思考 mySQL4 的构建顺序 //1. 当构建一个混入对象时,构建顺序和 声明的顺序一致(从左到右),机制和类的继承一致.
//(1) Operate4.. //(2) Data4 //(3) DB4 //(4) File4
//看看当我们执行混入对象的方法时,它的执行顺序如何 //1. 执行方法时,是从右到左执行. println("mySQL4.insert(100) 执行顺序.......") mySQL4.insert(100) //(1) 向文件 向数据库 //(2) 向数据库 向文件 //(3) 插入数据 = 100 插入数据 = 100
println("----------------------------------------") //val mySQL4_ = new MySQL4 with File4 with DB4 //(1) Operate4.. //(2) Data4 //(3) File4 //(4) DB4
} } class MySQL4 {} trait Operate4 { println("Operate4...") def insert(id : Int) } trait Data4 extends Operate4 { println("Data4") override def insert(id : Int): Unit = { println("插入数据 = " + id) } } trait DB4 extends Data4 { println("DB4") override def insert(id : Int): Unit = { println("向数据库") super.insert(id) } } trait File4 extends Data4 { println("File4") override def insert(id : Int): Unit = { println("向文件") //Scala中特质中如果调用super,并不是表示调用父特质的方法, // 而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找 super.insert(id) }} |
- 对上面代码的小结
1) 当构建一个混入对象时,构建顺序和 声明的顺序一致(从左到右),机制和类的继承一致
2) 执行方法时,是从右到左执行(按特质)
3) Scala中特质的方法中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
- 叠加特质注意事项和细节
1) 特质声明顺序从左到右。
2) Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
3) Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
4) 如果想要调用具体特质的方法,可以指定:super[特质].xxx(…).其中的泛型必须是该特质的直接超类类型
8.4.8 在特质中重写抽象方法
- 提出问题
- 解决问题
- 重写抽象方法时需要考虑混入特质的顺序问题和完整性问题
package com.atguigu.chapter08.traitpart.traitabstractoverride02
object TraitAbstractOverride { def main(args: Array[String]): Unit = { //当我们重新的特质的抽象方法(还调用super...) //考虑动态混入的顺序问题 var mysql2 = new MySQL5 with DB5 mysql2.insert(100)
// 错误 var mysql3 = new MySQL5 with File5
// var mysql4 = new MySQL5 with File5 with DB5 // mysql4.insert(200)
//这里混入时,就考虑的混入的顺序和完整性. var mysql4 = new MySQL5 with DB5 with File5 mysql4.insert(200) } }
trait Operate5 { def insert(id: Int) //抽象方法 }
trait File5 extends Operate5 { //使用的是abstract override abstract override def insert(id: Int): Unit = { println("将数据保存到文件中..") super.insert(id) } }
trait DB5 extends Operate5 { def insert(id: Int): Unit = { println("将数据保存到数据库中..") } } class MySQL5 {} |
8.4.9 当作富接口使用的特质
8.4.10 特质中的具体字段
8.4.11 特质构造顺序
8.4.12 扩展类的特质
- 特质可以继承自类,以用来拓展该类的一些功能
- 所有混入该特质的类,会自动成为那个特质所继承的超类的子类
- 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误
8.4.13 自身类型
- 说明
自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的 类的类型。
- 应用案例
举例说明自身类型特质,以及如何使用自身类型特质
8.5 嵌套类
8.5.1 基本介绍
在Scala中,你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类,这样的类是嵌套类,其他语法结构也是一样。
嵌套类类似于Java中的内部类。
package com.atguigu.chapter08.innerclass
object InnerClass01 { def main(args: Array[String]): Unit = { //创建成员内部类对象 //1. 先创建ScalaOuterClasss实例 val outer01 = new ScalaOuterClass val outer02 = new ScalaOuterClass //2. 创建ScalaInnerClasss实例 //说明 //1. 在scala中 成员内部类的类型默认是和外部对象关联 //2. 即 outer01.ScalaInnerClass类型 val inner01 = new outer01.ScalaInnerClass var inner02 = new outer02.ScalaInnerClass println(inner01 + " " + inner02)
//创建静态内部类实例 val staticInner = new ScalaOuterClass.ScalaStaticInnerClass() println(staticInner)
} } class ScalaOuterClass { //外部类 class ScalaInnerClass { //成员内部类 } }
object ScalaOuterClass { //伴生对象 class ScalaStaticInnerClass { //静态内部类 } }
|
请编写程序,在内部类中访问外部类的属性。
- 方式1
内部类如果想要访问外部类的属性,可以通过外部类对象访问。
即:访问方式:外部类名.this.属性名
代码:
package com.atguigu.chapter08.innerclass
object InnerClass02 { def main(args: Array[String]): Unit = { //创建成员内部类对象 //1. 先创建ScalaOuterClasss实例 val outer01 = new ScalaOuterClass val outer02 = new ScalaOuterClass //2. 创建ScalaInnerClasss实例 //说明 //1. 在scala中 成员内部类的类型默认是和外部对象关联 //2. 即 outer01.ScalaInnerClass类型 val inner01 = new outer01.ScalaInnerClass var inner02 = new outer02.ScalaInnerClass //println(inner01 + " " + inner02) inner01.info() //scott 1.2 }
}
class ScalaOuterClass { var name: String = "scott" private var sal: Double = 1.2
class ScalaInnerClass { //成员内部类 def info() = { // 访问方式:外部类名.this.属性名 // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例, // 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性 // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法. println("name = " + ScalaOuterClass.this.name + " age =" + ScalaOuterClass.this.sal) } } }
|
请编写程序,在内部类中访问外部内的属性。
- 方式2
内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。
即:访问方式:外部类名别名.属性名
8.5.2 类型投影
- 先看一段代码,引出类型投影
- 使用类型投影解决
解决方式-使用类型投影
类型投影是指:在方法声明上,如果使用 外部类#内部类 的方式,表示忽略内部类的对象关系,等同于Java中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型)【案例演示】
8.6 数组的基本使用
package com.atguigu.chapter08.innerclass
object ArrayTest { def main(args: Array[String]): Unit = { //scala的数组的基本时候用 //不可变数组 //使用方式1 //创建 val arr01 = Array(10,20,30) //创建数组时,直接赋值 //遍历 for (item <- arr01) { println(item) } //修改 arr01(0) = 90 for (item <- arr01) { println(item) }
//方式2 val arr02 = new Array[Int](10) //默认为0 println("--------arr02--------") for (item <- arr02) { println(item) } arr02(9) = 90 for (item <- arr02) { println(item) }
} }
|
第 9 章 隐式转换和隐式参数
9.1 隐式转换
9.1.1 提出问题
9.1.2 隐式函数基本介绍
隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型
9.1.3 隐式函数快速入门
9.1.4 隐式函数的底层工作原理
9.1.5 隐式转换的注意事项和细节
1) 隐式转换函数的函数名可以是任意的,隐式转换与函数名称无关,只与函数签名(函数参数类型和返回值类型)有关。
2) 隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别
9.2 隐式转换丰富类库功能
9.2.1 基本介绍
如果需要为一个类增加一个方法,可以通过隐式转换来实现。(动态增加功能)比如想为MySQL类增加一个delete方法
9.2.2 分析解决方案
在当前程序中,如果想要给MySQL类增加功能是非常简单的,但是在实际项目中,如果想要增加新的功能就会需要改变源代码,这是很难接受的。而且违背了软件开发的OCP开发原则 【open close princeple】
在这种情况下,可以通过隐式转换函数给类动态添加功能。
9.2.3 快速入门案例
9.3 隐式值
9.3.1 基本介绍
隐式值也叫隐式变量,将某个形参变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数
9.3.2 应用案例
9.3.3 课堂测试题
9.3.4 课堂练习
package com.atguigu.chapter09.implicitpart
object ImplicitExer02 {
def main(args: Array[String]): Unit = { // 隐式变量(值) implicit val name: String = "Scala" implicit val name1: String = "World"
def hello(implicit content: String = "jack"): Unit = { println("Hello " + content) } //调用hello hello } } |
- 对上面代码的小结
1) 隐式值的优先级高于默认值
2) 当匹配到多个隐式值就会报错(匹配时,是安装类型匹配)
9.4 隐式类
9.4.1 基本介绍
在scala2.10后提供了隐式类,可以使用implicit声明类,隐式类的非常强大,同样可以扩展类的功能,比前面使用隐式转换丰富类库功能更加的方便,在集合中隐式类会发挥重要的作用。
9.4.2 隐式类使用有如下几个特点:
1) 其所带的构造参数有且只能有一个
2) 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是 顶级的(top-level objects)。
3) 隐式类不能是case class(case class在后续介绍)
4) 作用域内不能有与之相同名称的标示符
9.4.3 应用案例
9.5 隐式的转换时机
1) 当方法中的参数的类型与目标类型不一致时
2) 当对象调用所在类中不存在的方法或成员时,编译器会自动将对象进行隐式转换(根据类型)
9.6 隐式解析机制
9.7 隐式转换的前提
在进行隐式转换时,需要遵守两个基本的前提:
1) 不能存在二义性
2) 隐式操作不能嵌套 // [举例:]如:隐式转换函数
第 10 章
数据结构上-集合
10.1 数据结构的特点
10.1.1 scala集合基本介绍
1) Scala同时支持不可变集合和可变集合,不可变集合可以安全的并发访问
2) 两个主要的包:
不可变集合:scala.collection.immutable
可变集合: scala.collection.mutable
3) Scala默认采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本
10.1.2 可变集合和不可变集合举例
1) 不可变集合:scala不可变集合,就是这个集合本身不能变[内存地址不能变](类似java的数组,是不可以动态增长的)
2) 可变集合:可变集合(ArrayList , 是可以动态增长的) 就是这个集合的本身是可以变的[内存地址可变],成为一个新的集合.
3) 以java举例说明
package com.atguigu.chapter10.collection;
import java.util.ArrayList; public class JavaCollection { public static void main(String[] args) {
//不可变集合类似java的数组 int[] nums = new int[3]; nums[2] = 11; //? //nums[3] = 90; //?
// String[] names = {"bj", "sh"}; // System.out.println(nums + " " + names); // // //可变集合举例 ArrayList al = new ArrayList<String>(); al.add("zs"); al.add("zs2"); System.out.println(al + " " + al.hashCode()); //地址 al.add("zs3"); System.out.println(al + " " + al.hashCode()); //地址 } } |
10.2 不可变集合继承层次一览
- 对上面图的一个小结
1.Set、Map是Java中也有的集合
2.Seq是Java没有的,我们发现List归属到Seq了,因此这里的List就和java不是同
一个概念了
3.我们前面的for循环有一个 1 to 3 ,就是IndexedSeq 下的Vector
4.String也是属于IndexeSeq
5.我们发现经典的数据结构比如Queue 和 Stack被归属到LinerSeq
6.大家注意Scala中的Map体系有一个SortedMap,说明Scala的Map可以支持
排序
7.IndexSeq 和 LinearSeq 的区别[IndexSeq是通过索引来查找和定位,
因此速度快,比如String就是一个索引集合,通过索引即可定位]
[LineaSeq 是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找,
它的价值在于应用到一些具体的应用场景
10.3 Scala可变集合继承关系一览图
10.4 数组-定长数组(声明泛型)
10.4.1 第一种方式定义数组
这里的数组等同于Java中的数组,中括号的类型就是数组的类型
val arr1 = new Array[Int](10)
//赋值,集合元素采用小括号访问
arr1(1) = 7
- 案例演示
package com.atguigu.chapter10.arraypart
object ArrayDemo01 { def main(args: Array[String]): Unit = { //说明 //1. new 是关键字,[Int] 是指定可以存放的数据类型 //2. 如果希望存放任意数据类型,则指定Any //3. (4) ,表示数组的大小,确定后就不可以变化 //4. 在底层 Array 对应的代码是 /* int[] nums = new int[3]; nums[2] = 11; */ val arr01 = new Array[Int](4) println(arr01.length) // 4 arr01(3) = 10 //修改某个元素的值 //遍历数组 for (i <- arr01) { println(i) } } } |
|
10.4.2 第二种方式定义数组
- 说明
在定义数组时,直接赋值
//使用apply方法创建数组对象
val arr1 = Array(1, 2)
- 举例说明
//第二种创建数组的方式apply var arr02 = Array(1, 3, "xxx") for (i <- arr02) { println(i) } |
10.5 数组-变长数组(声明泛型)
10.5.1 基本使用和应用案例
package com.atguigu.chapter10.arraypart
import scala.collection.mutable.ArrayBuffer
object ArrayBuffer01 { def main(args: Array[String]): Unit = { //说明 //1. ArrayBuffer 需要引入 scala.collection.mutable.ArrayBuffer //2. ArrayBuffer[Any](3, 2, 5) 的 (3, 2, 5) 初始元素有三个 //3. ArrayBuffer是有序的集合!!!! //4. 增加元素使用的是 append方法(),支持可变参数。 val arr01 = ArrayBuffer[Any](3, 2, 5) //遍历数组 for (i <- arr01) { println(i) } println(arr01.length) // 3 //println("arr01.hash=" + arr01.hashCode())
//增加了元素,使用的方法是 //def append(elems: A*) { appendAll(elems) } arr01.append(90.0,13) //println("arr01.hash=" + arr01.hashCode())
arr01(1) = 89 //修改第2个元素的值 println("--------------------------") for (i <- arr01) { println(i) } println(arr01.length) // 5 } }
|
10.5.2 定长数组与变长数组的转换
arr1.toBuffer //定长数组转可变数组
arr2.toArray //可变数组转定长数组
- 说明:
1) arr2.toArray 返回结果才是一个定长数组, arr2本身没有变化
2) arr1.toBuffer返回结果才是一个可变数组, arr1本身没有变化
- 案例演示
package com.atguigu.chapter10.arraypart
import scala.collection.mutable.ArrayBuffer
object Array22ArrayBuffer { def main(args: Array[String]): Unit = { //创建一个空的可变数组 val arr2 = ArrayBuffer[Int]() // 追加值 arr2.append(1, 2, 3) println(arr2) // 1,2,3
// ArrayBuffer ==> Array //说明 //1. arr2.toArray 返回的结果是一个新的定长数组集合 //2. arr2它没有变化 val newArr = arr2.toArray
println(newArr) // // Array ===> ArrayBuffer //说明 //1. newArr.toBuffer 返回一个变长数组 newArr2 //2. newArr 没有任何变化,依然是定长数组 val newArr2 = newArr.toBuffer newArr2.append(123) println(newArr2)
} }
|
10.6 数组-多维数组
10.6.1 多维数组的定义和使用
- 定义
val arr = Array.ofDim[Double](3,4)
//说明:二维数组中有三个一维数组,每个一维数组中有四个元素
- 案例演示
package com.atguigu.chapter10.arraypart
object DimArray { def main(args: Array[String]): Unit = { //创建一个二维数组[3,4] //说明 //1. 创建了一个 二维数组, 有三个元素,每个元素是,含有4个元素一维数组() //2. 在底层仍然是java的方式处理 // int[][] arr = (int[][])Array..MODULE$.ofDim(3, 4, ClassTag..MODULE$.Int()); // arr[1][2] = 88; val arr = Array.ofDim[Int](3, 4) arr(1)(2) = 88 //遍历二维数组 for (i <- arr) { //i 就是一维数组 for (j <- i) { print(j + " ") } println() }
} }
|
10.6.2 关于特质的接受参数(多态的)补充
package com.atguigu.chapter10.arraypart
object TraitUse { def main(args: Array[String]): Unit = { //测试: val a01 = new A B.test(a01) // b ok.. } }
trait MyTrait01 {} //特质 //A类继承MyTrait01 class A extends MyTrait01 {}
object B { //test方法,可以接受一个继承MyTrait01的类的实例(这点和java一样,体现接口多态) def test(m: MyTrait01): Unit = { println("b ok..") } }
|
10.7 数组-Scala数组与Java数组的互转
10.7.1 Scala数组转Java数组(List)
在项目开发中,有时我们需要将Scala数组转成Java数组,看下面案例:
package com.atguigu.chapter10.arraypart
import scala.collection.mutable.ArrayBuffer
object ArraytoArrayList { def main(args: Array[String]): Unit = {
//创建了ArrayBuffer val arr = ArrayBuffer("1", "2", "3")
//下面的import 引入了我们需要的隐式函数【这里就是隐式函数的应用】 //implicit def bufferAsJavaList[A](b : scala.collection.mutable.Buffer[A]) : java.util.List[A] import scala.collection.JavaConversions.bufferAsJavaList
//这里使用了我们的隐式函数bufferAsJavaList完成两个ArrayBuffer -> List 转换 //返回的就是 List<String> val javaArr = new ProcessBuilder(arr)
//返回的是 List<String> val arrList = javaArr.command() println(arrList) //输出 [1, 2, 3] } }
|
10.7.2 Java数组(List)转Scala数组
在项目开发中,有时我们需要将Java数组转成Scala数组,看下面案例
- 案例如下:
//=============================================================================
//Java数组(List)转Scala数组
import scala.collection.JavaConversions.asScalaBuffer import scala.collection.mutable println("---------------------Java数组(List)转Scala数组--------------------------") // java.util.List ==> Buffer val scalaArr: mutable.Buffer[String] = arrList println(scalaArr) |
10.8 元组Tuple-元组的基本使用
10.8.1 基本介绍
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。
说的简单点,就是将多个无关的数据封装为一个整体,称为元组
注意:元组中最大只能有22个元素
10.8.2 元组的创建
package com.atguigu.chapter10.tuplepart
object TupleDemo01 { def main(args: Array[String]): Unit = { //说明 //1. 创建了一个元组 tuple1 类似 Tuple4 //2. 元组最多有 22个元素, Tuple1 ---->Tuple22 //3. 获取元组的元素的方法 tuple._1 表示取出第一个元素,依次类推 //4. 下面的代码的底层 Tuple4 tuple1 val tuple1 = (1, 2, 3, "hello") // 底层 Tuple4 tuple1
println(tuple1) println(tuple1._1 + " " + tuple1._2)
} } |
10.9 元组Tuple-元组数据的访问
10.9.1 基本介绍
访问元组中的数据,可以采用顺序号(_顺序号),也可以通过索引(productElement)访问。
10.9.2 应用案例
/* override def productElement(n: Int) = n match { case 0 => _1 case 1 => _2 case 2 => _3 case 3 => _4 case _ => throw new IndexOutOfBoundsException(n.toString()) } */ println(tuple1.productElement(3)) //等价 tuple1._4,即第四个元素. |
10.10 元组Tuple-元组数据的遍历
使用元组的迭代器进行遍历.
println("----------------遍历元组---------------------") //遍历元组 for (item <- tuple1.productIterator) { println("item=" + item) } |
10.11 列表 List-创建List
10.11.1 基本介绍
Scala中的List 和Java List 不一样,在Java中List是一个接口,真正存放数据是ArrayList,而Scala的List可以直接存放数据,就是一个object,默认情况下Scala的List是不可变的。
val List = scala.collection.immutable.List
object List extends SeqFactory[List]
10.11.2 创建List的应用案例
package com.atguigu.chapter10.listpart
object ListCreate { def main(args: Array[String]): Unit = {
//为什么可以直接使用List ,和 Nil? //原因是 在 package object scala 有声明 /* val List = scala.collection.immutable.List val Nil = scala.collection.immutable.Nil */
val list1 = List(1,2,3) println("list1=" + list1) //空list的创建 val list2 = Nil println("list2=" + list2) //List()
} }
|
10.11.3 列表 List-访问List元素
//访问list的元素 //list(1) 表示的下标 是从0开始的. val e = list1(1) println("e=" + e) //2 |
10.11.4 列表 List-元素的追加
- 基本说明
向列表中增加元素, 会返回新的列表/集合对象。注意:Scala中List元素的追加形式非常独特,和Java不一样。
- 方式1-在列表最后追加数据
var list1 = List(1, 2, 3, "abc") // :+运算符表示在列表的最后增加数据4 // 说明 :+ 是追加符号 //1. : 这边是集合, + 这边是元素. val list2 = list1 :+ 4 println(list1) //list1没有变化 println(list2) //新的列表结果是 [1, 2, 3, "abc", 4] |
- 方式2-在列表的最前面追加数据
println("-------------------------------------") // +:运算符表示在列表的最前面增加数据40 // 说明 :+ 是追加符号 val list3 = 40 +: list1 println("list1=" + list1) //list1没有变化 (1, 2, 3, "abc") println("list3=" + list3) //List(40, 1, 2, 3, "abc") |
10.11.5 方式3-在列表的最后增加数据
- 说明:
1) 符号::表示向集合中 新建集合添加元素。
2) 运算时,集合对象一定要放置在最右边,
3) 运算规则,从右向左。
4) ::: 运算符是将集合中的每一个元素加入到空集合中去[要求::: 左右两边都是集合]
- 应用案例
package com.atguigu.chapter10.listpart
object ListAdd3 { def main(args: Array[String]): Unit = {
println("---------------案例演示------------------") //list1是一个列表 val list1 = List(1, 2, 3, "abc")
//步骤如下 //1. () 空集合 //2. ((1, 2, 3, "abc")) //3 (6, (1, 2, 3, "abc")) //4 (5,6, (1, 2, 3, "abc")) //5 (4,5,6, (1, 2, 3, "abc")) val list5 = 4 :: 5 :: 6 :: list1 :: Nil println("list5=" + list5)// 4,5,6, (1, 2, 3, "abc") //
//步骤 //1 (1, 2, 3, "abc") //2 (6, 1, 2, 3, "abc") //3 (5, 6, 1, 2, 3, "abc") //4 (4, 5, 6, 1, 2, 3, "abc") val list6 = 4 :: 5 :: 6 :: list1
println("list6=" + list6)
//步骤 //1. () //2. (1, 2, 3, "abc") //3. (6,1, 2, 3, "abc") //4. (5,6,1, 2, 3, "abc") //5. (4,5,6,1, 2, 3, "abc") val list7 = 4 :: 5 :: 6 :: list1 ::: Nil //4 :: 5 :: 6 :: list1 println("list7=" + list7) //案例1 + 说明
println("-----------课堂练习。。。----------------")
val list11 = List(1, 2, 3, "abc") val list55 = 4 :: 5 :: 6 :: list11 println(list55) //(4, 5, 6, 1, 2, 3, abc)
//错误! ::: 【6 ::: list12】 6是集合 // val list12 = List(1, 2, 3, "abc") // val list52 = 4 :: 5 :: 6 ::: list12 ::: Nil // println(list5)
val list1_ = List(1, 2, 3, "abc") val list5_ = 4 :: 5 :: list1_ ::: list1_ ::: Nil println("list5_=" + list5_) // (4,5,1, 2, 3, "abc",1, 2, 3, "abc")
} }
|
10.12 队列 Queue-基本介绍
10.12.1 基本介绍
10.12.2 队列的说明
1) 队列是一个有序列表,在底层可以用数组或是链表来实现。
2) 其输入和输出要遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
3) 在Scala中,由设计者直接给我们提供队列类型使用
10.12.3 应用案例
- 创建队列
- 队列中添加数据
// 想q1中增加元素 20 // 说明 // 1. += 函数 def +=(elem: A): this.type = { appendElem(elem); this } // 2. q1 += 20 底层 q1.$plus$eq(20) // 3. 队列中,可以有重复数据 q1 += 20 //q1.$plus$eq(20) q1 += 20 //q1.$plus$eq(20) println(q1)
//说明 //1. 将List(2,4,6) 取出,然后加入到q1 q1 ++= List(2,4,6) println(q1) // (20,20,2,4,6) |
10.13 队列 Queue-删除队列元素
- 说明
- 案例
//删除队列的元素 //1. 删除的是队列的第一个元素 //2. 队列本身发生变化 q1.dequeue() println("q1=" + q1) |
10.14 队列 Queue-给队列添加元素
- 案例
//使用方法添加元素 q1.enqueue(10,90,11) println("q1=" + q1) // (20,20,2,4,6,10,90,11) |
10.15 队列 Queue-返回队列的元素
10.15.1 返回队列的第一个元素
//返回队列的头
val q2 = new mutable.Queue[Int] q2 ++= List(1,2,3) //说明 //执行 q2.head ,返回队列头元素,但是对队列本身没有影响 println("q2.head=" + q2.head) // 1 println("q2=" + q2) |
10.15.2 返回队列最后一个元素
println(q1.last)
10.15.3 返回队列的尾部
即:返回除了第一个以外剩余的元素, 可以级联使用,这个在递归时使用较多。
//返回队列尾部数据 // 说明 //1. q2.tail 返回的是一个队列 [从q2的第一个元素后的所有元素] //2. q2本身没有变化 //3. tail可以级联适应 val q3 = q2.tail println("q3=" + q3) //(2,3) |
10.16 关于上边界和下边界的知识点
10.17 映射 Map-基本介绍
10.17.1 Java中的Map回顾
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射,Java中的HashMap是无序的。
- 案例演示:
public class JavaMap { public static void main(String[] args) { HashMap<String,Integer> hm = new HashMap(); hm.put("no1", 100); hm.put("no2", 200); hm.put("no3", 300); hm.put("no4", 400);
System.out.println(hm); System.out.println(hm.get("no2"));
} }
|
10.18 Scala中的Map介绍
Scala中的Map 和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的Map是无序的。
10.19 映射 Map-构建Map
10.19.1 方式1-构造不可变映射
Scala中的不可变Map是有序,构建Map中的元素底层是Tuple2类型
- 案例演示
package com.atguigu.chapter10.mappart
object ScalaMap { def main(args: Array[String]): Unit = {
val map = Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30) println("map=" + map) //1. 取值, 知道key取值 println(map("Bob")) // 20 //2. 遍历map for (v <- map) { println("v=" + v) println("v.key=" + v._1 + " v.value=" + v._2) } } }
|
- 对上面代码的小结
1.从输出的结果看到,输出顺序和声明顺序一致
2.构建Map集合中,集合中的元素其实是Tuple2类型
3.默认情况下(即没有引入其它包的情况下),Map是不可变map
4.为什么说Map中的元素是Tuple2 类型 [反编译或看对应的apply]
10.19.2 方式2-构造可变映射
案例:
//构建可变的map //说明 //1. 可变的需要带包 //2. 可以是无序 val map2 = mutable.Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30) println("map2=" + map2) |
10.19.3 方式3-创建空的映射
val map3 = new scala.collection.mutable.HashMap[String, Int]
println(map3)
10.19.4 方式4-对偶元组
即创建包含键值对的二元组, 和第一种方式等价,只是形式上不同而已。
对偶元组 就是只含有两个数据的元组。
//以元组的方式构建map对象 val map3 = mutable.Map(("Alice" , 10), ("Bob" , 20), ("Kotlin" , 30)) println("map3=" + map3) |
10.20 映射 Map-取值
10.20.1 方式1-使用map(key)
val value1 = map1("Alice")
println(value1)
说明:
1) 如果key存在,则返回对应的值
2) 如果key不存在,则抛出异常
3) 在Java中,如果key不存在则返回null
10.20.2 方式2-使用contains方法检查是否存在key
- 基本说明
// 返回Boolean
// 1.如果key存在,则返回true
// 2.如果key不存在,则返回false
map4.contains("B")
- 案例
//方式2,先判断是否有key,然后做相应的处理 if (map3.contains("A")) { println("存在" + map3("A")) } else { println("key存在") } |
10.20.3 方式3-使用map.get(key).get取值
通过 映射.get(键) 这样的调用返回一个Option对象,要么是Some,要么是None
- 案例演示
/方式3: 是个get的方式来取值 var map4 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) ) println(map4.get("A")) //Some val some1 = map4.get("A") for (item<-some1) { println("item=" + item) } println(map4.get("A").get) //得到Some在取出 println("uuukey=" + map4.get("uuu"))// None |
- 案例说明
1) map.get方法会将数据进行包装
2) 如果 map.get(key) key存在返回some,如果key不存在,则返回None
3) 如果map.get(key).get key存在,返回key对应的值,否则,抛出异常 java.util.NoSuchElementException: None.get
10.20.4 方式4-使用map4.getOrElse()取值
- 说明
getOrElse 方法 : def getOrElse[V1 >: V](key: K, default: => V1)
说明:
1) 如果key存在,返回key对应的值。
2) 如果key不存在,返回默认值。在java中底层有很多类似的操作
- 案例
//方式4:使用getOrElse() val map5 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) ) println(map5.getOrElse("A1","默认")) //1 |
10.20.5 选择使用取值的方式建议
1) 如果你确定key存在,则直接使用map(), 或者 get()即可
2) 如果不确定key是否存在,但是只是想得到一个值,则使用getOrElse即可
3) 如果不确定key是否存在,而且是当key不存在时,需要对应的业务逻辑则时候用constrait判断..
10.21 映射 Map-对map修改、添加和删除
10.21.1 更新map的元素
案例:
//更新map的元素 val map6 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) ) //如果key存在,则修改 //如果key不存在,则添加 map6("A") = 20 println("map6=" + map6) |
- 对代码的说明
1) map 是可变的,才能修改,否则报错
2) 如果key存在:则修改对应的值,key不存在,等价于添加一个key-val
10.21.2 添加map元素
- 方式1-增加单个元素
案例
//第1种添加,添加单对 key-val //说明 //1. 如果key不存在,则添加 //2. 如果key存在,则修改 val map7 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) ) map7 += ( "D" -> 4 ) map7 += ( "B" -> 50 ) println("map7=" + map7) |
- 添加多个元素
//也可以添加多个key-val map7 += ("UU"->1, "FF"->3) |
10.21.3 删除map元素
- 案例
//删除元素的操作 //说明 //1. 可以删除多个key //2. 如果key存在,则删除,如果key不存在,就不会抛出异常 val map8 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) ) map8 -= ("A", "BB") println("map8=" + map8) |
- 代码说明
1) "A","B" 就是要删除的key, 可以写多个.
2) 如果key存在,就删除,如果key不存在,也不会报错.
10.22 映射 Map-对map遍历
- 案例演示
//遍历map val map9 = mutable.Map(("A", 1), ("B", "北京"), ("C", 3)) //方式1 for ((k, v) <- map9) { print("k=" + k + "v=" + v + "\t") } //方式2,只遍历key for (k <- map9.keys) { print("k=" + k + "\t") } //方式3,只遍历values for (v <- map9.values) { print("v=" + v + "\t") } //方式4,取出每个元素(Tuple2) for (item <- map9) { println("item=" + item) // println("k=" + item._1 + "v=" + item._2) }
|
10.23 集 Set-基本介绍
集是不重复元素的结合。集不保留顺序,默认是以哈希集实现
10.23.1 Java中Set的回顾
java中,HashSet是实现Set<E>接口的一个实体类,数据是以哈希表的形式存放的,里面的不能包含重复数据。Set接口是一种一个不包含重复元素的 collection,HashSet中的数据也是没有顺序的
具体的代码
package com.atguigu.chapter10.setpart; import java.util.*; public class JavaSet { public static void main(String[] args) { //说明 //1. 元素不能重复 //2. 没有顺序 HashSet hs = new HashSet<String>(); hs.add("jack"); hs.add("tom"); hs.add("jack"); hs.add("jack2"); System.out.println(hs);
} } |
10.23.2 Scala中Set的说明
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包
10.24 集 Set-创建
10.24.1 可变set 和不可变set的创建
object ScalaSet { def main(args: Array[String]): Unit = {
//1. 创建一个Set // //默认是不可变集, 无序 val set01 = Set(1,2,4,"abc", 20, 13.4) println("set01=" + set01) //默认是可变集, 无序 val set02 = mutable.Set(1,2,4,"abc", 20, 67.8) println("set02=" + set02)
} } |
10.24.2 可变集合的元素添加
//可变Set的添加 //1. 如果添加的对象已经存在,则不会重复添加,也不会报错 set02 += 11 set02 += 11 set02 += 11 //这种方式,就添加后,返回一个新的Set //set02.+(90) println("set02=" + set02) // 不可变的Set 不能执行 += , //set01 += 80
|
- 代码小结
1) 1. 如果添加的对象已经存在,则不会重复添加,也不会报错
2) set02.+(90)返回新的集合
10.24.3 可变集合的元素删除
val set02 = mutable.Set(1,2,4,"abc")
set02 -= 2
set02.remove("abc")
println(set02)
说明:如果删除的对象不存在,则不生效,也不会报错
10.24.4 集Set的遍历
val set02 = mutable.Set(1, 2, 4, "abc")
for(x <- set02) {
println(x)
}
10.25 集 Set-更多操作
第 11 章 数据结构下-集合操作
11.1 看一个实际需求
1) 要求:请将list(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回,即返回一个新的list(6,10,14), 请编写程序实现.
2) 使用传统的方法解决
11.2 集合元素的映射-map映射操作
11.2.1 讲解了高阶函数的基本使用
package com.atguigu.chapter11.mapoper
//高阶函数基本使用
object HighOrderDef { def main(args: Array[String]): Unit = {
val res = test(sum, 2.0) println("res=" + res) // 4.0
//调用高阶函数test2 test2(myPrint)
}
//test函数的说明 //1.test函数可以接受一个函数,test函数就是高阶函数 //2.f: Double => Double : f就是形参名,表示函数 ,Double:传入的函数可以形参(Double) // => 一个规定 Double 接受的函数的返回值. //3. n1: Double :普通的形参 def test(f: Double => Double, n1: Double) = { f(n1) //执行 }
def sum(d1: Double): Double = { d1 * 2 }
//再写一个高阶函数的使用 def test2(f: () => Unit): Unit = { f() }
def myPrint(): Unit = { println("hello,world!") } } |
11.2.2 使用map映射函数来解决
package com.atguigu.chapter11.mapoper
object MapOperDemo { def main(args: Array[String]): Unit = {
//使用传统的方式 //1. 优点 简单,好理解 //2. 缺点: 不够简洁,高效,没有使用函数式编程 // val list1 = List(3, 5, 7) // var list2 = List[Int]() // for (item <- list1) {//遍历 // list2 = list2 :+ item * 2 // } // println(list2)
//使用map映射函数解决 val list1 = List(3, 5, 7) //对list1.map(mul) 解释 //1. 将 list1集合元素遍历,传给mul,进行运算 2 * n //2. 将运算的结果放入到一个新的集合中,并返回 val list2 = list1.map(mul) println("list2=" + list2) // (6, 10, 14)) }
//写成函数 def mul(n: Int): Int = { 2 * n } }
|
11.2.3 课堂练习
请将 val names = List("Alice", "Bob", "Nick") 中的所有单词,全部转成字母大写,返回到一下新的List集合中.
代码:
package com.atguigu.chapter11.mapoper
object MapExercise01 { def main(args: Array[String]): Unit = { val names = List("Alice", "Bob", "Nick") def upper(s:String): String = { s.toUpperCase } val names2 = names.map(upper) println("names=" + names2) // } }
|
11.2.4 flatmap映射:flat即压扁,压平,扁平化映射
- 基本介绍
flatmap:flat即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合。
- 看一个案例:
object FlatMap { def main(args: Array[String]): Unit = {
val names = List("Alice", "Bob", "Nick") def upper( s : String ) : String = { s. toUpperCase } val names2 = names.flatMap(upper) println("names2=" + names2) //List(A, L, I, C, E, B, O, B, N, I, C, K) } } |
- 对代码的简单分析
//注意:每个字符串也是char集合
println(names.flatMap(upper)) //map 遍历 【 "Alice", "Bob", "Nick" 】
//flatMap (A,l,i,c,e, ....) ==upper===>(A,L,I)
11.3 集合元素的过滤-filter
filter:将符合要求的数据(筛选)放置到新的集合中
- 应用案例:
将 val names = List("Alice", "Bob", "Nick") 集合中首字母为'A'的筛选到新的集合。
package com.atguigu.chapter11.mapoper
object FilterMap { def main(args: Array[String]): Unit = { val names = List("Alice", "Bob", "Nick") //普通函数 //1. 如果s是以"A" 开头的,我就返回true, 否则返回false def startA(s:String): Boolean = { s.startsWith("A") }
//2. 使用filter来对集合进行过滤操作 // 当我们的names的每个元素,会调用startA进行判断,如果为true,就放入到新集合 val names2 = names.filter(startA) println("names2=" + names2) // List("Alice")
} }
|
11.4 化简
11.4.1 看一个需求:
val list = List(1, 20, 30, 4 ,5) , 求出list的和.
11.4.2 化简:
- 化简:将二元函数引用于集合中的函数,。
- 上面的问题当然可以使用遍历list方法来解决,这里我们使用scala的化简方式来完成。[案例演示+代码说明]
- 代码实现:
package com.atguigu.chapter11.mapoper
object reduce { def main(args: Array[String]): Unit = { val list = List(1, 20, 30, 4, 5) //sum 返回两个数的和 def sum(n1: Int, n2: Int): Int = { n1 + n2 } //分析执行流程.
//1, 20, 30, 4, 5 //1. (1,20) 30, 4,5 //2 (21 30), 4,5 //3. (51 , 4), 5 //4. 55 , 5 //5. 60
val res = list.reduceLeft(sum) println("res=" + res) // 60
//(1, (20, (30, (4, 5)))) val res2 = list.reduceRight(sum) println("res2=" + res2) // 60
} }
|
11.4.3 reduceLefft(_ - _)这个函数的执行逻辑如图
reduceRight() 和 上面一样,只是从右边开始进行简化.
11.4.4 课堂练习题
package com.atguigu.chapter11.mapoper
object ReduceExe { def main(args: Array[String]): Unit = { val list = List(1, 2, 3, 4 ,5) def minus( num1 : Int, num2 : Int ): Int = { num1 - num2 } println("r1=" + list.reduceLeft(minus)) // 输出? -13 println("r2=" + list.reduceRight(minus)) //输出? 3 println("r3=" + list.reduce(minus)) //输出? 3 从左化简
} }
|
- 使用化简的方法求出 List(3,4,2,7,5) 最小的值
package com.atguigu.chapter11.mapoper
object ReduceExe02 { def main(args: Array[String]): Unit = { /* 使用化简的方法求出 List(3,4,2,7,5) 最小的值
*/ def minVal(n1:Int,n2:Int): Int = { if (n1 > n2) n2 else n1 } val list1 = List(3,4,2,7,5) println("最小值=" + list1.reduceLeft(minVal))
} }
|
11.5 折叠
11.5.1 基本介绍
fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的所有元素被遍历。
- 可以把reduceLeft看做简化版的foldLeft。
如何理解:
def reduceLeft[B >: A](@deprecatedName('f) op: (B, A) => B): B =
if (isEmpty) throw new UnsupportedOperationException("empty.reduceLeft")
else tail.foldLeft[B](head)(op)
大家可以看到. reduceLeft就是调用的foldLeft[B](head),并且是默认从计划的head元素开始操作的。 - 相关函数:fold,foldLeft,foldRight,可以参考reduce的相关方法理解
11.5.2 应用案例
看下面代码看看输出什么,并分析原因.
package com.atguigu.chapter11.mapoper
object FoldDemo01 { def main(args: Array[String]): Unit = { val list = List(1, 2, 3, 4)
def minus(num1: Int, num2: Int): Int = { num1 - num2 } //执行流程分析(((5, 1), 2), 3, 4) println(list.foldLeft(5)(minus)) //-5 //执行流程分析:(1, 2, 3, 4) (3) println(list.foldRight(5)(minus)) //3
} }
|
11.5.3 foldLeft和foldRight 缩写方法分别是:/:和:\
package com.atguigu.chapter11.mapoper
object FoldDemo02 { def main(args: Array[String]): Unit = {
val list4 = List(1, 9, 2, 8)
def minus(num1: Int, num2: Int): Int = { num1 - num2 } // /: 等价 foldLeft var i6 = (0 /: list4) (minus) // (-20) (1,9,2,8) println(i6) // 输出?
// :\ 等价 foldRight i6 = (list4 :\ 10 )(minus) // (1,9,2,8) (-4) println(i6) // 输出? // -4
} }
|
11.6 扫描
11.6.1 基本介绍
扫描,即对某个集合的所有元素做fold操作,但是会把产生的所有中间结果放置于一个集合中保存
11.6.2 案例演示
package com.atguigu.chapter11.mapoper
object ScanDemo { def main(args: Array[String]): Unit = { def minus( num1 : Int, num2 : Int ) : Int = { num1 - num2 } //5 (1,2,3,4,5) =>(5,4,2,-1,-5,-10) val i8 = (1 to 5).scanLeft(5)(minus) //Vector(5, 4, 2, -1, -5, -10) println(i8) def add( num1 : Int, num2 : Int ) : Int = { num1 + num2 } //5 (1,2,3,4,5) =>(5,6,8, 11,15,20) val i9 = (1 to 5).scanLeft(5)(add) //Vector(5, 6, 8, 11, 15, 20) println(i9)
} }
|
11.6.3 课堂练习
请写出下面的运行结果
def test(n1:Int ,n2 :Int): Int = {
n1 * n2
}
val i10 = (1 to 3).scanLeft(3)(test)
println("i10=" + i10) // (3,3,6,18)
11.7 课堂练习1
val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
将sentence 中各个字符,通过foldLeft存放到 一个ArrayBuffer中
目的:理解flodLeft的用法.
package com.atguigu.chapter11.mapoper
import scala.collection.mutable.ArrayBuffer
object Exercise03 { def main(args: Array[String]): Unit = { val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
def putArry( arr : ArrayBuffer[Char], c : Char ): ArrayBuffer[Char] = { arr.append(c) arr } //创建val arr = ArrayBuffer[Char]() val arr = ArrayBuffer[Char]() println(sentence.foldLeft(arr)(putArry))
} }
|
11.8 课堂练习2
val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
使用映射集合,统计一句话中,各个字母出现的次数
提示:Map[Char, Int]()
看看java如何实现
使用scala的flodLeft折叠方式实现.
课后完成思考:使用Scala的传统方法来实现(类似Java)
package com.atguigu.chapter11.mapoper
object Exercise04 { def main(args: Array[String]): Unit = {
val sentence = "AAAAAGAAAAABBBBBBBBCCCCCDD" def charCount( map : Map[Char, Int], c : Char ): Map[Char, Int] = { map + (c -> (map.getOrElse(c, 0) + 1)) } val map2 = sentence.foldLeft(Map[Char, Int]())(charCount) println(map2)
} } |
11.9 扩展-拉链(合并)
11.9.1 基本介绍
在开发中,当我们需要将两个集合进行 对偶元组合并,可以使用拉链。
11.9.2 应用实例
package com.atguigu.chapter11.zippart
object ZipDemo {
def main(args: Array[String]): Unit = {
val list1 = List(1, 2 ,3) val list2 = List(4, 5, 6) val list3 = list1.zip(list2) //((1,4), (2,5),(3,6))
println("list3=" + list3)
}
}
|
11.10 扩展-迭代器
11.10.1 基本说明
通过iterator方法从集合获得一个迭代器,通过while循环和for表达式对集合进行遍历
11.10.2 应用案例
package com.atguigu.chapter11.iterator
object IteratorDemo { def main(args: Array[String]): Unit = { val iterator = List(1, 2, 3, 4, 5).iterator // println("--------遍历方式1 -----------------") while (iterator.hasNext) { println(iterator.next()) } println("--------遍历方式2 for -----------------") for(enum <- iterator) { println(enum) // }
} }
|
11.10.3 对iterator的使用的说明
1) iterator 的构建实际是 AbstractIterator 的一个匿名子类,该子类提供了
/*
def iterator: Iterator[A] = new AbstractIterator[A] {
var these = self
def hasNext: Boolean = !these.isEmpty
def next(): A =
*/
2) 该AbstractIterator 子类提供了 hasNext next 等方法.
3) 因此,我们可以使用 while的方式,使用hasNext next 方法变量
11.11 扩展-流 Stream
11.11.1 基本说明
stream是一个集合。这个集合,可以用于存放无穷多个元素,但是这无穷个元素并不会一次性生产出来,而是需要用到多大的区间,就会动态的生产,末尾元素遵循lazy规则(即:要使用结果才进行计算的) 。
11.11.2 创建Stream对象
- 案例:
def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)
val stream1 = numsForm(1)
package com.atguigu.chapter11.stream
object StreamDemo { def main(args: Array[String]): Unit = { //对下面的函数说明 //Stream 集合存放的数据类型是BigInt //numsForm 是自定义的一个函数,函数名是程序员指定的。 //创建的集合的第一个元素是 n , 后续元素生成的规则是 n + 1 //后续元素生成的规则是可以程序员指定的 ,比如 numsForm( n * 4)...
def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1) val stream1 = numsForm(1) println("stream1=" + stream1) //我希望在取一个流集合数据 println(stream1.tail) //(2,?) println("stream1=" + stream1) // (1,2,?) println("stream1.head=" + stream1.head)
//println("stream1.last" + stream1.last) //死循环 } }
|
- 说明
Stream 集合存放的数据类型是BigInt
numsForm 是自定义的一个函数,函数名是程序员指定的。
创建的集合的第一个元素是 n , 后续元素生成的规则是 n + 1
后续元素生成的规则是可以程序员指定的 ,比如 numsForm( n * 4)...
11.12 扩展-视图 View
11.12.1 基本介绍
Stream的懒加载特性,也可以对其他集合应用view方法来得到类似的效果,具有如下特点:
view方法产出一个总是被懒执行的集合。
11.12.2 view不会缓存数据,每次都要重新计算
应用案例
请找到1-100 中,数字倒序排列 和它本身相同的所有数。
package com.atguigu.chapter11.view
object ViewDemo { def main(args: Array[String]): Unit = {
//函数 将一个数字,原封不动的返回 def multiple(num: Int): Int = { num }
//判断一个数字,交换顺序后,是否相等 def eq(i: Int): Boolean = { i.toString.equals(i.toString.reverse) }
//说明: 没有使用view val viewSquares1 = (1 to 100) .map(multiple) .filter(eq) println(viewSquares1) //(1,2,3,。。。。11,22)
//说明: 没有使用view val viewSquares2 = (1 to 100) .view .map(multiple) .filter(eq) println("viewSquares2=" + viewSquares2) //(SeqView)lazy for (item <- viewSquares2) { print(item + ",") }
// //for (x <- viewSquares1) {} // //使用view // val viewSquares2 = (1 to 100) // .view // .map(multiple) // .filter(eq) // println(viewSquares2)
} }
|
11.13 扩展-线程安全的集合
11.14 扩展-并行集合
11.14.1 基本介绍
Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。
- 主要用到的算法有:
Divide and conquer : 分治算法,Scala通过splitters,combiners等抽象层来实现,主要原理是将计算工作分解很多任务,分发给一些处理器去完成,并将它们处理结果合并返回
Work stealin算法,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的 - 应用案例
package com.atguigu.chapter11.parallel
object ParallelDemo2 { def main(args: Array[String]): Unit = { val result1 = (0 to 100).map{case _ => Thread.currentThread.getName} val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}.distinct println(result1) println(result2)
} }
|
11.15 扩展-操作符
11.15.1 基本介绍
这部分内容没有必要刻意去理解和记忆,语法使用的多了,自然就会熟练的使用,该部分内容了解一下即可。
11.15.2 操作符扩展
1) 如果想在变量名、类名等定义中使用语法关键字(保留字),可以配合反引号反引号 [案例演示]
val `val` = 42
2) 中置操作符:A 操作符 B 等同于 A.操作符(B)
val dog = new Dog dog.+(10) dog + 20
println(dog.age) // 40
class Dog { var age:Int = 10 def +(n:Int): Unit = { age += n } } |
3) 后置操作符:A操作符 等同于 A.操作符,如果操作符定义的时候不带()则调用时不能加括号
class Operate {
//定义函数/方法的时候,省略的() def ++ = "123"
}
//后置操作符 val oper = new Operate println(oper++) // println(oper.++) |
4) 前置操作符,+、-、!、~等操作符A等同于A.unary_操作符 [案例演示]
val oper2 = new Operate2 !oper2 //
class Operate2 { // 声明前置运算符 //unary :一元运算符 def unary_! = println("!!!!!!!") } |
5) 赋值操作符,A 操作符= B 等同于 A = A 操作符 B ,比如 A += B 等价 A = A + B
第 12 章
模式匹配
12.1 match
12.1.1 基本介绍
Scala中的模式匹配类似于Java中的switch语法,但是更加强大。
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句
12.1.2 Java swtich应用案例
12.1.3 scala的match应用案例
- 案例: 给你两个数,根据运算符得到相应的结果,使用match-case
package com.atguigu.chapter12.matchpart
object MatchDemo01 { def main(args: Array[String]): Unit = { /* 给你两个数,根据运算符得到相应的结果,使用match-case */ val n1 = 10 val n2 = 20 var res = 0 val operChar = '+' operChar match { case '+' => { res = n1 + n2 print("xx") } case '-' => res = n1 - n2 case '*' => res = n1 * n2 case '/' => res = n1 / n2 case _ => println("你的运算符有误..") } println("res=" + res) // 30 } } |
12.1.4 match的细节和注意事项
1) 如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句
2) 如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError
3) 每个case中,不用break语句,自动中断case
4) 可以在match中使用其它类型(任意类型),而不仅仅是字符
5) => 等价于 java swtich 的 :
6) => 后面的代码块到下一个 case, 是作为一个整体执行,可以使用{} 扩起来,也可以不扩。
12.2 守卫
12.2.1 基本介绍
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫
12.2.2 应用案例
package com.atguigu.chapter12.matchpart
object MatchGuard { def main(args: Array[String]): Unit = { for (ch <- "+-3!") { //遍历字符串 var sign = 0 var digit = 0 ch match { case '+' => sign = 1 case '-' => sign = -1 // 说明 // 1. 如果 ch.toString.equals("3") 为真,则表示匹配成功 case _ if ch.toString.equals("3") => digit = 3 case _ if ch.toInt > 4 => println("大于4") case _ => sign = 2 } println(ch + " " + sign + " " + digit) }
} }
|
12.2.3 课堂思考题
12.3 模式中的变量
12.3.1 基本介绍
如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量
12.3.2 案例
package com.atguigu.chapter12.matchpart
object MatchVar { def main(args: Array[String]): Unit = { val ch = 'B' //说明 //1. match的前面是一个表达式(即任何有值的即可) ch+1+f1() match { // ch 变量 case '+' => println("ok~") //说明,当代码走到 case mychar ,就会将 ch赋给mychar case mychar => println("ok~~~~~" + mychar) case _ => println ("ok~~") }
} def f1(): Char = { 'D' } }
|
12.4 类型匹配
12.4.1 基本介绍
可以匹配对象的任意类型,这样做避免了使用isInstanceOf和asInstanceOf方法
12.4.2 应用案例
package com.atguigu.chapter12.matchpart
object MatchType { def main(args: Array[String]): Unit = { //下面的代码的作用是,根据 a的值不同,返回不同数据类型 val a = 3 val obj = if(a == 1) 1 else if(a == 2) "2" else if(a == 3) BigInt(3) else if(a == 4) Map("aa" -> 1) else if(a == 5) Map(1 -> "aa") else if(a == 6) Array(1, 2, 3) else if(a == 7) Array("aa", 1) else if(a == 8) Array("aa")
//使用类型匹配来进行操作 val result = obj match {
case a : Int => a //说明 case b : Map[String, Int] => "对象是一个字符串-数字的Map集合" //1. 先将 obj 赋给 b , b的名称就是一个普通变量名,程序员指定 //2. Map[String, Int] 类型 //3. 如果类型匹配成功,就会执行 => 后面的代码 case b : Map[String, Int] => "对象是一个字符串-数字的Map集合" case c : Map[Int, String] => "对象是一个数字-字符串的Map集合" case d : Array[String] => "对象是一个字符串数组" case e : Array[Int] => "对象是一个数字数组" case f : BigInt => Int.MaxValue case _ => "啥也不是" }
println("res=" + result)
} }
|
12.4.3 类型匹配注意事项
1) Map[String, Int] 和Map[Int, String]是两种不同的类型,其它类推。
2) 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错.
val obj = 10
val result = obj match {
case a : Int => a
case b : Map[String, Int] => "Map集合"
case _ => "啥也不是"
}
3) 如果 case _ 出现在match 中间,则表示隐藏变量名,即不使用,而不是表示默认匹配
//使用类型匹配来进行操作 val result = obj match { //表示隐藏变量 case _ : Int => println("int被匹配") } |
12.5 匹配数组
12.5.1 基本介绍
1) Array(0) 匹配只有一个元素且为0的数组。
2) Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。当然可以依次类推Array(x,y,z) 匹配数组有3个元素的等等...
3) Array(0,_*) 匹配数组以0开始
12.5.2 应用案例
package com.atguigu.chapter12.matchpart
object MatchArray { def main(args: Array[String]): Unit = { 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) 这个数组 // 说明 //1. 匹配有两个元素的数组,然后将 将元素值赋给对应的x,y //2. => 使用xy case Array(x, y) => x + "=" + y //说明 //1. 以0开头和数组 case Array(0, _*) => "以0开头和数组" case _ => "什么集合都不是" } println("result = " + result) }
} }
|
12.6 匹配列表
12.6.1 应用案例
package com.atguigu.chapter12.matchpart
object MatchList { def main(args: Array[String]): Unit = { //list是一个存放List集合的数组 //请思考,如果要匹配 List(88) 这样的只含有一个元素的列表,并原值返回.应该怎么写 for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0),List(88))) { val result = list match {
case 0 :: Nil => "0" //匹配List(0) case x :: y :: Nil => x + " " + y //匹配有两个元素的List case 0 :: tail => "0 ..." //匹配以0开头的,后面有0到多个元素的List case x :: Nil => x::Nil case _ => "something else"
} println(result) }
} }
|
12.7 匹配元组
12.7.1 应用案例
package com.atguigu.chapter12.matchpart
object MatchTuple {
def main(args: Array[String]): Unit = { //对一个元组集合进行遍历 for (pair <- Array((0, 1), (1, 0), (1, 1),(1,0,2))) { val result = pair match { // case (0, _) => "0 ..." //是第一个元素是0的元组 case (y, 0) => y // 匹配后一个元素是0的对偶元组 case (a,b) => a + " " + b case _ => "other" //.默认 } println(result) }
} }
|
12.8 对象匹配
12.8.1 基本介绍
对象匹配,什么才算是匹配呢?,规则如下:
1) case中对象的unapply方法(提取器)返回some集合则为匹配成功
2) 返回none集合则为匹配失败
12.8.2 应用案例1
package com.atguigu.chapter12.matchpart
object MatchObject { def main(args: Array[String]): Unit = { object Square { //我们要编写出正确的构造器和对象提取器,前提是你的业务逻辑是正确 def unapply(z: Double): Option[Double] = Some(math.sqrt(z)) def apply(z: Double): Double = z * z } // 模式匹配使用: //val number: Double = 8.0 val n = Square(9.0) //构建了一个Square n match { //说明 // 1. 当case 后面有Square(n),调用 Square的 unapply,进行对象提取 // 注意:unapply方法的参数 unapply(z: Double)的z 是number // 2. 如果返回的unapply 返回的是Some,就认为匹配成功 // 3. 如果返回的是None ,就认为不成功 // 4. 匹配成功后,会把返回值,赋给 Square(n) 的n case Square(n) => println("匹配到=" + n) case _ => println("nothing matched") } } }
|
12.8.3 应用案例1的小结
1) 构建对象时apply会被调用 ,比如 val n1 = Square(5)
2) 当将 Square(n) 写在 case 后时[case Square(n) => xxx],会默认调用unapply 方法(对象提取器)
3) number 会被 传递给def unapply(z: Double) 的 z 形参
4) 如果返回的是Some集合,则unapply提取器返回的结果会返回给 n 这个形参
5) case中对象的unapply方法(提取器)返回some集合则为匹配成功
6) 返回none集合则为匹配失败
12.8.4 应用案例2
package com.atguigu.chapter12.matchpart
object MatchObject02 { def main(args: Array[String]): Unit = {
val namesString = "Alice,Bob" //说明 // 1. 对namesString 进行对象匹配
namesString match { //1. 这里Names(first, second, third)就会调用 unapplySeq //2. unapplySeq 如果返回了some集合,就会一次将结果赋给first, second, third //3. unapplySeq 返回的some集合中的结果的个数要和 case 提取的参数匹配 //4. 返回none 表示匹配失败,即对象提取失败 case Names(first, second, third) => { println("the string contains three people's names ok!") // 打印字符串 println(s"$first $second $third") } case _ => println("nothing matched") }
} }
object Names { //条件满足,返回 Some,条件不满足,返回None def unapplySeq(str: String): Option[Seq[String]] = { if (str.contains(",")) Some(str.split(",")) else None } }
|
12.8.5 应用案例2的小结
1) 当case 后面的对象提取器方法的参数为多个,则会默认调用def unapplySeq() 方法
2) 如果unapplySeq返回是Some,获取其中的值,判断得到的sequence中的元素的个数是否是三个,如果是三个,则把三个元素分别取出,赋值给first,second和third
3) 其它的规则不变.
12.9 变量声明中的模式
12.9.1 基本介绍
match中每一个case都可以单独提取出来,意思是一样的.
12.9.2 应用案例
package com.atguigu.chapter12.matchpart
object VarPattern { def main(args: Array[String]): Unit = {
val (x, y) = (1, 2) // x = 1 y = 2 val (q, r) = BigInt(10) /% 3 //说明 q = BigInt(10) / 3 r = BigInt(10) % 3 val arr = Array(1, 7, 2, 9) val Array(first, second, _*) = arr println(first, second) println("q=" + q + " r=" + r) //案例演示+说明
} }
|