大数据开发必备技能,Scala 零基础笔记
一、scala的定义
语言特点
Scala是一门以Java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言(静态语言需要提前编译的如:Java、c、c++等,动态语言如:js)
- Scala是一门多范式的编程语言,支持面向对象和函数式编程(多范式,就是多种编程方法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法)
- Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接
- Scala单作为一门语言来看,非常的简洁高效
- Scala在设计时,马丁·奥德斯基是参考了Java的设计思想,可以说Scala是源于Java,同时马丁·奥德斯基也加入了自己的思想,将函数式编程语言的特点融合到Java中, 因此,对于学习过Java的同学,只要在学习Scala的过程中,搞清楚Scala和Java相同点和不同点,就可以快速的掌握Scala这门语言
环境搭建及插件安装
1.环境搭建
- 首先确保JDK1.8安装成功
- 下载对应的 Scala 安装文件并解压
- 配置Scala的环境变量
2.插件安装
- 在线安装(推荐)
- 在搜索插件框里面输入 Scala->点击 Install->点击 ok->点击 apply
- 离线安装
- 将插件放在Scala的安装目录下(这里就不放资源了,有需要的小伙伴可上网下载,网上的这种资源一搜就有)
- 打开IDEA,点击左上角File->点击下拉框的Settings…->点击Plugins->点击右下角Install plugin from disk… , 找到自己对应的插件存储路径,最后点击ok即可
HelloWorld案例
- 打开 IDEA->点击左侧的 Flie->选择 New->选择 Project…
- 创建一个 Maven 工程,并点击 next
- 输入自定义的GroupId和ArtifactId,然后点击Next(工程存储路径一定不能有中文和空格)
- 指定项目工作目录空间
- Maven默认不支持Scala的开发,需要引入Scala的框架
- 在自己创建的项目上,右击键->Add Framework Support…->选择Scala->点击OK(如果是第一次引入需要选择自己的Scala安装目录,工具就会自动识别)
- 创建项目的源文件目录
- 右键点击main目录->New->点击Diretory->写个名字(比如 scala)
- 右键点击scala目录->Mark Directory as->选择Sources root,可以看到文件夹颜色发生了变化
- 在scala包下,创建包com.demo包名和Hello类名
1 | package com.demo |
1 | hello word |
二、变量和数据类型
注释
Scala注释使用和Java完全一样
1 | //单行注释 |
变量和常量
常量指在程序执行的过程中,其值不会被改变的变量
- 基本语法
- var 变量名 [: 变量类型] = 初始值
- var i : Int = 10
- val 常量名 [: 常量类型] = 初始值
- val j : Int = 20
- Scala中能用常量的地方不用变量
- var 变量名 [: 变量类型] = 初始值
- 案例
1 | def main(args: Array[String]): Unit = { |
1 | bob 24 atguigu |
标识符的命名规范
Scala 对各种变量、方法、函数等命名时使用的字符序列称为标识符(凡是自己可以起名字的地方都叫标识符)
- 以字母或者下划线开头,后接字母、数字、下划线
- 以操作符开头,且只包含操作符(+ - * / # !等)
- 用反引号`…`包括的任意字符串,即使是 Scala 关键字(39 个)也可以
字符串输出
- 基本语法
- 字符串,通过+号连接
- printf 用法:字符串,通过%传值
- 字符串模板(插值字符串):通过$获取变量值
- 案例实操
1 | def main(args: Array[String]): Unit = { |
1 | 18岁的alice |
键盘输入
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取
- 基本语法
- StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
- 案例实操
1 | def main(args: Array[String]): Unit = { |
1 | 请输入您的姓名: |
文件的读写操作
1 | def main(args: Array[String]): Unit = { |
1 | hello world |
数据类型
- Scala中一切数据都是对象,都是Any的子类
- Scala中数据类型分为两大类:数值类型(AnyVal)、引用类型(AnyRef),不管是值类型还是引用类型都是对象
- Scala数据类型仍然遵守,低精度的值类型向高精度值类型自动转换(隐式转换)
- Scala中的StringOps是对Java中的String增强
- Unit:对应Java中的void,用于方法返回值的位置,表示方法没有返回值。Unit是 一个数据类型,只有一个对象就是()。Void不是数据类型,只是一个关键字
- Null是一个类型,只有一个对象就 是null。它是所有引用类型(AnyRef)的子类
- Nothing是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数
1.整数类型(Byte、Short、Int、Long)
Scala的整数类型就是用于存放整数值的,比如12,30,3456等
数据类型 | 描述 |
---|---|
Byte[1] | 8位有符号补码整数,数值区间为 -128到127 |
Short[2] | 16位有符号补码整数,数值区间为 -32768到32767 |
Int[4] | 32位有符号补码整数,数值区间为 -2147483648到2147483647 |
Long[8] | 64位有符号补码整数,数值区间为 -9223372036854775808 到 9223372036854775807 = 2 的(64-1)次方-1 |
- Scala各整数类型有固定的表示范围和字段长度,不受具体操作的影响,以保证
Scala 程序的可移植性
1 | def main(args: Array[String]): Unit = { |
- Scala 的整型,默认为Int型,声明Long型,须后加‘l’或‘L’
1 | def main(args: Array[String]): Unit = { |
1 | 10 |
- Scala程序中变量常声明为Int型,除非不足以表示大数,才使用Long
2.浮点类型(Float、Double)
Scala的浮点类型可以表示一个小数,比如 123.4f,7.8,0.12 等等
数据类型 | 描述 |
---|---|
Float [4] | 32位, IEEE 754 标准的单精度浮点数 |
Double [8] | 64位 IEEE 754 标准的双精度浮点数 |
- Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加“f”或“F”
1 | def main(args: Array[String]): Unit = { |
1 | n7=2.2345679 |
3.字符类型(Char)
字符类型(Char)可以表示单个字符
- 字符常量是用单引号 ’ ’ 括起来的单个字符
- \t :一个制表位,实现对齐的功能
- \n :换行符
- \\ :表示 \
- \" :表示"
1 | def main(args: Array[String]): Unit = { |
1 | c1=a |
4.布尔类型(Boolean)
- 布尔类型也叫Boolean类型,Boolean类型数据只允许取值true和false
- Boolean类型占1个字节
1 | def main(args: Array[String]): Unit = { |
5.Unit类型、Null类型和Nothing类型
数据类型 | 描述 |
---|---|
Unit | 表示无值,和其他语言中 void 等同。用作不返回任何结果的方法的结果类型。Unit 只有一个实例值,写成() |
Null | null , Null 类型只有一个实例值 null |
Nothing | Nothing 类型在 Scala 的类层级最低端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,可以用 Nothing 来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性) |
- Unit 类型用来标识过程,也就是没有明确返回值的函数
- 由此可见,Unit 类似于 Java 里的 void,Unit 只有一个实例——( ),这个实例也没有实质意义
1 | def main(args: Array[String]): Unit = { |
1 | () |
- Null 类只有一个实例对象,Null 类似于 Java 中的 null 引用。Null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
1 | def main(args: Array[String]): Unit = { |
- Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于 Nothing 是其他任意类型的子类,他还能跟要求返回值的方法兼容
1 | def main(args: Array[String]): Unit = { |
类型转换
1.类型自动转换
当 Scala 程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)。数据类型按精度(容量)大小排序为:
- 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算
- 把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换
- (byte,short)和 char 之间不会相互自动转换
- byte,short,char 这三者可以计算,在计算时首先转换为 int 类型
1 | def main(args: Array[String]): Unit = { |
1 | 3.0 |
2.强制类型转换
自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出
1 | def main(args: Array[String]): Unit = { |
1 | r1=36 r2=44 |
3.数值类型和String类型转换
在程序开发中,经常需要将基本数值类型转成 String 类型,或者将 String 类型转成基本数值类型
- 基本类型转 String 类型(语法:将基本类型的值+“” 即可)
- String 类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
1 | def main(args: Array[String]): Unit = { |
注意事项:在将 String 类型转成基本数值类型时,要确保 String 类型能够转成有效的数据,比如我们可以把"123",转成一个整数,但是不能把"hello"转成一个整数
三、运算符
算术运算符
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | b=4; -b | -4 |
+ | 加 | 5+5 | 10 |
- | 减 | 6-4 | 2 |
* | 乘 | 3*4 | 12 |
/ | 除 | 5/5 | 1 |
% | 取模(取余) | 7%5 | 2 |
+ | 字符串相加 | "He"+"llo" | "hello" |
关系运算符
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
== | 相等于 | 4==3 | false |
!= | 不等于 | 4 != 3 | true |
< | 小于 | 4<3 | false |
> | 大于 | 4>3 | true |
<= | 小于等于 | 4<=3 | false |
>= | 大于等于 | 4>=3 | true |
- Java
- ==比较两个变量本身的值,即两个对象在内存中的首地址
- equals比较字符串中所包含的内容是否相同
1 | public static void main(String[] args) { |
1 | false |
- Scala
- ==更类似于Java中equals
1 | def main(args: Array[String]): Unit = { |
1 | true |
逻辑运算符
用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个Boolean值
设:变量A为true,B为false
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑与 | (A && B) 运算结果为 false |
|| | 逻辑或 | (A || B) 运算结果为 true |
! | 逻辑非 | !(A && B) 运算结果为 true |
赋值运算符
赋值运算符就是将某个运算后的值,赋给指定的变量
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值给 C |
+= | 相加后再赋值 | C += A 等于 C = C + A |
-= | 相减后再赋值 | C -= A 等于 C = C - A |
*= | 相乘后再赋值 | C *= A 等于 C = C * A |
/= | 相除后再赋值 | C /= A 等于 C = C / A |
%= | 求余后再赋值 | C %= A 等于 C = C % A |
<<= | 左移后赋值 | C <<= 2 等于 C = C << 2 |
>>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 |
&= | 按位与后赋值 | C &= 2 等于 C = C & 2 |
^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 |
|= | 按位或后赋值 | C |= 2 等于 C = C | 2 |
注:Scala中没有++、–操作符,可以通过+=、-=来实现同样的效果
位运算符
设:表中变量a为60,b为13
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与运算符 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| | 按位或运算符 | (a | b) 输出结果 61 ,二进制解释: 0011 1101 |
^ | 按位异或运算符 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
~ | 按位取反运算符 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式 |
<< | 左移动运算符 | a << 2 输出结果 240 ,二进制解释: 0011 0000 |
>> | 右移动运算符 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
>>> | 无符号右移 | a >>>2 输出结果 15, 二进制解释: 0000 1111 |
运算符的本质
在Scala中其实是没有运算符的,所有运算符都是方法
- 当调用对象的方法时,点.可以省略
- 如果函数参数只有一个,或者没有参数,()可以省略
四、if、for及while循环
if分支控制
1.单分支
- 基本语法
1 | if (条件表达式) { |
说明:当条件表达式为true时,就会执行{ }的代码
- 案例
- 输入人的年龄,如果年龄小于 18 岁,则输出“未成年”
1 | def main(args: Array[String]): Unit = { |
1 | input age: |
2.双分支
- 基本语法
1 | if (条件表达式) { |
- 案例
- 输入年龄,如果年龄小于 18 岁,则输出“未成年”,否则,输出“成年”
1 | def main(args: Array[String]): Unit = { |
1 | input age: |
3.多分支
- 基本语法
1 | if (条件表达式 1) { |
- 案例1
- 输入年龄,如果年龄小于 18 岁,则输出“未成年”,如果年龄大于等于 18 且小于等于 30,则输出“中年”,否则,输出“老年”
1 | def main(args: Array[String]): Unit = { |
1 | input age: |
- 案例2
- Scala 中 if-else 表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容
1 | def main(args: Array[String]): Unit = { |
1 | input age: |
- 案例3
- Scala 中返回值类型不一致,取它们共同的祖先类型
1 | def main(args: Array[String]): Unit = { |
1 | input age: |
- 案例4
- 可以用 if-else 实现Java中的三元运算符
- 如果大括号{}内的逻辑代码只有一行,大括号可以省略,省略后,if 只对最近的一行逻辑代码起作用
1 | def main(args: Array[String]): Unit = { |
1 | input age: |
4.嵌套分支
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层,分支外面的分支结构称为外层分支(嵌套分支不要超过3层)
- 基本语法
1 | if(){ |
- 案例
- 如果输入的年龄小于 18,返回“未成年”,如果输入的年龄大于等于 18,需要再判断:如果年龄大于等于 18 且小于 30,返回“中年”,如果其他,返回“老年”
1 | def main(args: Array[String]): Unit = { |
1 | input age: |
for循环控制
1.范围数据循环(To)
- 基本语法
- i 表示循环的变量,<-规定to
- i 将会从1-3循环,前后闭合
1 | for(i <- 1 to 3){ |
1 | 1 2 3 |
- 案例
- 输出 5 句 “编程怎么这么难”
1 | def main(args: Array[String]): Unit = { |
1 | 编程怎么这么难1 |
2.范围数据循环(Until)
- 基本语法
- 和前面的区别在于 i 是从 1 到 3-1
- 即前闭合后开的范围
1 | for(i <- 1 to 3){ |
1 | 1 2 |
- 案例
- 输出 5 句 “编程怎么这么难”
1 | def main(args: Array[String]): Unit = { |
1 | 编程怎么这么难1 |
3.循环守卫
- 基本语法
1 | for(i <- 1 to 3 if i != 2) { |
1 | 1 3 |
- 说明
- 循环守卫,即循环保护式(也称条件判断式守卫),保护式为 true 则进入循环体内部,为 false 则跳过,类似于 continue
- 案例
- 输出 1 到 5 中,不等于 3 的值
1 | def main(args: Array[String]): Unit = { |
1 | 1 |
4.循环步长
- 基本语法
1 | for (i <- 1 to 10 by 2) { |
1 | i=1 |
说明:by表示步长
- 案例
- 输出 1 到 10 以内的所有奇数
1 | def main(args: Array[String]): Unit = { |
1 | i=1 |
5.嵌套循环
- 基本语法
1 | for(i <- 1 to 3; j <- 1 to 3) { |
1 | i =1 j = 1 |
说明:没有关键字,所以范围后一定要加 ; 来隔断逻辑
- 与上面的代码等价
1 | for (i <- 1 to 3) { |
6.引入变量
- 基本语法
1 | for(i <- 1 to 3; j = 4 - i) { |
1 | i=1 j=3 |
- 说明
- for 推导式一行中有多个表达式时,所以要加 ; 来隔断逻辑
- for 推导式有一个不成文的约定:当 for 推导式仅包含单一表达式时使用圆括号,当包含多个表达式时,一般每行一个表达式,并用花括号代替圆括号
1 | for { |
- 等价于上面的代码
1 | for (i <- 1 to 3) { |
7.循环返回值
- 基本语法
1 | val res = for(i <- 1 to 10) yield i |
说明:将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字(开发中很少使用)
- 案例
- 将原数据中所有值乘以 2,并把数据返回到一个新的集合中
1 | def main(args: Array[String]): Unit = { |
1 | Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) |
8.倒叙打印
- 如果想倒序打印一组数据,可以用 reverse
1 | def main(args: Array[String]): Unit = { |
1 | 10 |
while和do…while循环控制
1.while循环控制
- 基本语法
1 | while (循环条件) { |
- 说明
- 循环条件是返回一个布尔值的表达式
- while 循环是先判断再执行语句
- 与 for 语句不同,while 语句没有返回值,即整个 while 语句的结果是Unit 类型()
- 因为 while 中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量,而变量需要声明在while 循环的外部,那么就等同于循环的内部对外部的变量造成了影响,所以不推荐使用,而是推荐使用 for 循环
- 案例
- 输出 10 句 “努力搬砖,从我做起”
1 | def main(args: Array[String]): Unit = { var i = 0 |
1 | 努力搬砖,从我做起0 |
2.do…while循环控制
- 基本语法
1 | do{ |
- 说明
- 循环条件是返回一个布尔值的表达式
- do…while 循环是先执行,再判断
- 案例
- 输出 10 句 “活到老,搬到老”
1 | def main(args: Array[String]): Unit = { var i = 0 |
1 | 活到老,搬到老0 |
3.循环中断
- 基本说明
- Scala 内置控制结构特地去掉了 break 和 continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break 和continue 的功能,而不是一个关键字。Scala 中使用breakable 控制结构来实现 break 和 continue 功能
- 案例1
- 采用异常的方式退出循环
1 | def main(args: Array[String]): Unit = { |
1 | 1 |
- 案例2
- 采用 Scala 自带的函数,退出循环
1 | import scala.util.control.Breaks |
1 | 1 |
- 案例3
- 对break 进行省略
1 | import scala.util.control.Breaks._ |
1 | 1 |
- 案例4
- 循环遍历 10 以内的所有数据,奇数打印,偶数跳过(continue)
1 | def main(args: Array[String]): Unit = { |
1 | 1 |
4.多重循环
- 基本说明
- 将 一 个 循 环 放 在 另 一 个 循 环 体 内 , 就 形 成 了 嵌 套 循 环 。 其 中 ,for,while,do…while均可以作为外层循环和内层循环
- 设外层循环次数为m 次,内层为 n 次,则内层循环体实际上需要执行 m*n 次
- 案例
- 打印九九乘法表
1 | def main(args: Array[String]): Unit = { |
1 | 1*1=1 |
五、函数式编程
- 面向对象编程
- 解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题
- 对象:用户
- 行为:登录、连接JDBC、读取数据库
- 属性:用户名、密码
- Scala 语言是一个完全面向对象编程语言:万物皆对象
- 对象的本质:对数据和行为的一个封装
- 函数式编程
- 解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题
- 例:请求->用户名、密码->连接 JDBC->读取数据库
- Scala 语言是一个完全函数式编程语言:万物皆函数
- 函数的本质:函数可以当做一个值进行传递
- 解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题
- Scala中函数式编程和面向对象编程完美的融合在一起了
函数基础
1.函数基本语法
- 基本语法
1 | def sum ( x: Int , y: Int) : Int = { |
- 说明
- def:定义函数的关键字
- sum:函数名
- x,y:参数名
- Int:参数类型
- Int:函数返回值类型
- x + y:函数体
- 案例
- 定义一个函数,将传入的名称打印出来
1 | def main(args: Array[String]): Unit = { |
1 | hello world |
2.函数与方法的区别
- 核心概念
- 为完成某一功能的程序语句的集合,称为函数
- 类中的函数称之方法
- 区别
- Scala 可以在任何的语法结构中声明任何的语法
- 函数没有重载和重写的概念,而方法可以进行重载和重写
- Scala 中函数可以嵌套定义
3.函数的定义
- 函数的定义
- 函数 1:无参,无返回值
- 函数 2:无参,有返回值
- 函数 3:有参,无返回值
- 函数 4:有参,有返回值
- 函数 5:多参,无返回值
- 函数 6:多参,有返回值
- 案例
1 | def main(args: Array[String]): Unit = { |
1 | 无参,无返回值 |
4.函数的参数
- 案例
- 可变参数
- 如果参数列表中存在多个参数,那么可变参数一般放置在最后
- 参数默认值,一般将有默认值的参数放置在参数列表的后面
- 带名参数
1 | def main(args: Array[String]): Unit = { |
1 | WrappedArray(Hello, Scala) |
5.至简原则
- 函数至简原则:能省就省
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
- 如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
- 案例
1 | def main(args: Array[String]): Unit = { |
1 | Hello jinlian |
函数进阶
1.高阶函数
- 函数可以作为值进行传递
1 | def main(args: Array[String]): Unit = { |
1 | foo... |
- 函数可以作为参数进行传递
1 | def main(args: Array[String]): Unit = { |
1 | 6 |
- 函数可以作为函数返回值返回
1 | def main(args: Array[String]): Unit = { |
2.匿名函数
- 说明
- 没有名字的函数就是匿名函数
- (x:Int)=>{函数体}
- x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑
- 案例1
- 传递的函数有一个参数
- 传递匿名函数至简原则
- 参数的类型可以省略,会根据形参进行自动的推导
- 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号
- 匿名函数如果只有一行,则大括号也可以省略
- 如果参数只出现一次,则参数省略且后面参数可以用_代替
- 传递的函数有一个参数
1 | def main(args: Array[String]): Unit = { |
1 | 2,3,4,5 |
- 案例2
- 传递的函数有两个参数
1 | def main(args: Array[String]): Unit = { |
1 | 5 |
3.高阶函数案例
- 模拟 Map 映射、Filter 过滤、Reduce 聚合
1 | def main(args: Array[String]): Unit = { |
1 | 1,4,9,16 |
4.函数柯里化&闭包
- 说明
- 闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
- 函数柯里化:把一个参数列表的多个参数,变成多个参数列表
- 案例
1 | def main(args: Array[String]): Unit = { |
1 | 13 |
5.递归
- 说明
- 一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
- 案例
1 | def main(args: Array[String]): Unit = { |
1 | 120 |
6.控制抽象
- 值调用:把计算后的值传递过去
1 | def main(args: Array[String]): Unit = { |
1 | f... |
- 名调用:把代码传递过去
1 | def main(args: Array[String]): Unit = { |
1 | f... |
注:Java 只有值调用;Scala 既有值调用,又有名调用
- 案例
1 | def main(args: Array[String]): Unit = { |
1 | aaa |
自定义一个 While 循环
1 | def main(args: Array[String]): Unit = { |
1 | 1 |
7.惰性加载
- 说明
- 当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数
- 案例
1 | def main(args: Array[String]): Unit = { |
1 | ---------------- |
注:lazy 不能修饰 var 类型的变量
六、面向对象
Scala包
- 基本语法
- package 包 名
- Scala包的三大作用(同Java相同)
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
1.包的命名
- 命名规则
- 只能包含数字、字母、下划线、小圆点.,但不能用数字开头,也不要使用关键字
- 案例
1 | demo.class.exec1 //错误,因为 class 关键字 |
- 命名规范
- 一般是小写字母+小圆点
- com.公司名.项目名.业务模块名
- 案例
1 | com.atguigu.oa.model |
2.包语句
- 说明
- Scala 有两种包的管理风格,一种方式和 Java 的包管理风格相同,每个源文件一个包(包名和源文件所在路径不要求必须一致),包名用“.”进行分隔以表示包的层级关系,如com.atguigu.scala。另一种风格,通过嵌套的风格表示层级关系,如下
1 | package |
- 第二种风格有以下特点
- 一个源文件中可以声明多个 package
- 子包中的类可以直接访问父包中的内容,而无需导包
- 案例
1 | package com { |
3.包对象
在 Scala 中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有 class 和 object 的共享变量,可以被直接访问
- 定义
1 | package object com{ |
- 说明
- 若使用 Java 的包管理风格,则包对象一般定义在其对应包下的 package.scala文件中,包对象名与包名保持一致
- 如采用嵌套方式管理包,则包对象可与包定义在同一文件中,但是要保证包对象与包声明在同一作用域中
1 | package com { |
4.导包说明
- Scala和 Java 一样,可以在顶部使用 import 导入,在这个文件中的所有类都可以使用
- 局部导入: 什么时候使用,什么时候导入
- 通配符导入: import java.util._
- 给类起名: import java.util.{ArrayList=>JL}
- 导入相同包的多个类: import java.util.{HashSet, ArrayList}
- 屏蔽类: import java.util.{ArrayList =>,}
- 导入包的绝对路径: new _root_.java.util.HashMap
1 | package java |
- 说明
import com.atguigu.Fruit | 引入 com.atguigu 包下Fruit(class 和 object) |
import com.atguigu._ | 引入 com.atguigu 下的所有成员 |
import com.atguigu.Fruit._ | 引入 Fruit(object)的所有成员 |
import com.atguigu.{Fruit,Vegetable} | 引入 com.atguigu 下的Fruit 和 Vegetable |
import com.atguigu.{Fruit=>Shuiguo} | 引入 com.atguigu 包下的 Fruit 并更名为 Shuiguo |
import com.atguigu.{Fruit=>Shuiguo,_} | 引入 com.atguigu 包下的所有成员,并将 Fruit 更名为 Shuiguo |
import com.atguigu.{Fruit=>_,_} | 引入 com.atguigu 包下屏蔽 Fruit 类 |
new _root_.java.util.HashMap | 引入的 Java 的绝对路径 |
注:Scala 中的三个默认导入分别是
- import java.lang._
- import scala._
- import scala.Predef._
类和对象
类: 可以看成一个模板
对象: 表示具体的事物
1.类的定义
- 基本语法
1 | [修饰符] class 类名 { |
- 说明
- Scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是public)
- 一个 Scala 源文件可以包含多个类
2.属性
属性是类的一个组成部分
- 基本语法
1 | [修饰符] var|val 属性名称 [:类型] = 属性值 |
注:Bean 属性(@BeanPropetry),可以自动生成规范的 setXxx/getXxx 方法
- 案例
1 | import scala.beans.BeanProperty |
1 | bobo |
3.访问权限
- 说明
- 在 Java 中,访问权限分为:public,private,protected 和默认。在 Scala 中,可以通过类似的修饰符达到同样的效果,但是使用上有区别
- (1)Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字
- (2)private 为私有权限,只在类的内部和伴生对象中可用
- (3)protected 为受保护权限,Scala 中受保护权限比 Java 中更严格,同类、子类可以访问,同包无法访问
- (4)private[包名]增加包访问权限,包名下的其他类也可以使用
- 案例
1 | package com.demo |
1 | bobo |
4.方法
- 基本语法
1 | def 方法名(参数列表) [:返回值类型] = { |
- 案例
1 | class Person { |
1 | 30 |
5.创建对象
- 基本语法
1 | val | var 对象名 [:类型] = new 类型() |
- 案例
- val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值
- var 修饰对象,可以修改对象的引用和修改对象的属性值
- 自动推导变量类型不能多态,所以多态需要显示声明
1 | class Person { |
1 | bobo |
6.构造器
和 Java 一样,Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala 类的构造器包括:主构造器和辅助构造器
- 基本语法
1 | class 类名(形参列表) { // 主构造器 |
- 说明
- 辅助构造器,函数的名称 this,可以有多个,编译器通过参数的个数及类型来区分
- 辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法
- 构造器调用其他另外的构造器,要求被调用构造器必须提前声明
- 案例
- 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
1 | //(1)如果主构造器无参数,小括号可省略 |
1 | 主构造器 |
7.构造器参数
- 说明
- Scala 类的主构造器函数的形参包括三种类型:未用任何修饰、var 修饰、val 修饰
- (1)未用任何修饰符修饰,这个参数就是一个局部变量
- (2)var 修饰参数,作为类的成员属性使用,可以修改
- (3)val 修饰参数,作为类只读属性使用,不能修改
- 案例
1 | class Person(name: String, var age: Int, val sex: String) { |
1 | 19 |
封装、继承和多态
1.封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。Java 封装操作如下:
- 将属性进行私有化
- 提供一个公共的 set 方法,用于对属性赋值
- 提供一个公共的 get 方法,用于获取属性的值
Scala 中的 public 属性,底层实际为 private,并通过 get 方法(obj.field())和 set 方法(obj.field_=(value))对其进行操作。所以 Scala 并不推荐将属性设为 private,再为其设置public 的 get 和 set 方法的做法。但由于很多 Java 框架都利用反射调用 getXXX 和 setXXX 方法,有时候为了和这些框架兼容,也会为 Scala 的属性设置 getXXX 和 setXXX 方法(通过@BeanProperty 注解实现)
2.继承和多态
- 基本语法
- 子类继承父类的属性和方法
- scala 是单继承
1 | class 子类名 extends 父类名 { 类体 } |
- 案例
- 继承的调用顺序:父类构造器->子类构造器
1 | class Person(nameParam: String) { |
1 | 父类主构造器 |
- 动态绑定
- Scala 中属性和方法都是动态绑定,而 Java 中只有方法为动态绑定
- 案例(对比Java与Scala的重写)
- Scala
1 | class Person { |
1 | teacher |
- Java
1 | class Person { |
1 | teacher |
抽象类
1.抽象属性和抽象方法
- 基本语法
- 定义抽象类: abstract class Person{} //通过 abstract 关键字标记抽象类
- 定义抽象属性: val|var name:String //一个属性没有初始化,就是抽象属性
- 定义抽象方法: def hello():String //只声明而没有实现的方法,就是抽象方法
- 案例
1 | abstract class Person { |
- 继承&重写
- 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
- 重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override
- 子类中调用父类的方法使用 super 关键字
- 子类对抽象属性进行实现,父类抽象属性可以用 var 修饰
- 子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var,因为 var 修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写
2.匿名子类
- 说明
- 和 Java 一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类
- 案例
1 | abstract class Person { |
单例对象(伴生对象)
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明
1.单例对象语法
- 基本语法
1 | object Person{ |
- 说明
- 单例对象采用object 关键字声明
- 单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致
- 单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问
- 案例
1 | //(1)伴生对象采用 object 关键字声明 |
2.apply方法
- 说明
- 通过伴生对象的 apply 方法,实现不使用 new 方法创建对象
- 如果想让主构造器变成私有的,可以在()之前加上 private
- apply 方法可以重载
- Scala 中 obj(arg)的语句实际是在调用该对象的 apply 方法,即 obj.apply(arg)。用以统一面向对象编程和函数式编程的风格
- 当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的 apply 方法
- 案例
1 | object Test { |
1 | apply 空参被调用 |
特质
Scala 语言中,采用特质 trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明
Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类
Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种补充
1.特质声明
- 基本语法
1 | trait 特质名 { |
- 案例
1 | trait PersonTrait { |
2.特质基本语法
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素, 所以在使用时,也采用了extends 关键字,如果有多个特质或存在父类,那么需要采用with 关键字连接
- 基本语法
1 | 没有父类:class 类名 extends 特质 1 with 特质 2 with 特质 3 … |
- 说明
- 类和特质的关系:使用继承的关系
- 当一个类去继承特质时,第一个连接词是 extends,后面是with
- 如果一个类在同时继承特质和父类时,应当把父类写在 extends 后
- 案例
- 特质可以同时拥有抽象方法和具体方法
- 一个类可以混入(mixin)多个特质
- 所有的 Java 接口都可以当做Scala 特质使用
- 动态混入:可灵活的扩展类的功能
- 动态混入:创建对象时混入 trait,而无需使类混入该 trait
- 如果混入的 trait 中有未实现的方法,则需要实现
1 | trait PersonTrait { |
1 | say |
3.特质叠加
由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。
冲突分为以下两种:
- 一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法
- 一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala 采用了特质叠加的策略
- 案例
- 所谓的特质叠加,就是将混入的多个 trait 中的冲突方法叠加起来
1 | trait Ball { |
1 | my ball is a blue-foot-ball |
4.特质叠加执行顺序
上面案例中的 super.describe()调用的是父 trait 中的方法吗?
当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的 super.describe()调用的实际上是排好序后的下一个特质中的 describe() 方法。排序规则如下:
- 结论
- 案例中的 super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass 中的 super 指代 Color,Color 中的 super 指代 Category,Category 中的 super指代 Ball
- 如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如super[Category].describe()
5.特质自身类型
- 说明
- 自身类型可实现依赖注入的功能
- 案例
1 | class User(val name: String, val age: Int) |
1 | login :bobo |
6.特质和抽象类的区别
- 优先使用特质:一个类扩展多个特质是很方便的,但却只能扩展一个抽象类
- 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)
扩展
1.类型转换和检查
- 说明
- obj.isInstanceOf[T]:判断 obj 是不是T 类型
- obj.asInstanceOf[T]:将 obj 强转成 T 类型
- classOf 获取对象的类名
- 案例
1 | class Person{ |
1 | com.demo.Person@16c0663d |
2.枚举类和应用类
- 说明
- 枚举类:需要继承 Enumeration
- 应用类:需要继承App
- 案例
1 | object Test { |
1 | red |
3.Type定义新类型
- 说明
- 使用 type 关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名
- 案例
1 | object Test { |
七、集合
集合简介
- Scala的集合有三大类:序列Seq、集合Set、映射Map,所有的集合都扩展自Iterable特质
- 对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两个包
- 不可变集合:scala.collection.immutable
- 可变集合: scala.collection.mutable
- Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而
不会对原对象进行修改。类似于 java 中的 String 对象 - 可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似
于 java 中 StringBuilder 对象
注:建议在操作集合时,不可变用符号,可变用方法
1.不可变集合继承图
- Set、Map 是 Java 中也有的集合
- Seq 是 Java 没有的,我们发现 List 归属到 Seq 了,因此这里的 List 就和 Java 不是同一个概念了
- 前面的 for 循环有一个 1 to 3,就是 IndexedSeq 下的 Range
- String 也是属于 IndexedSeq
- 经典的数据结构比如 Queue 和 Stack 被归属到 LinearSeq(线性序列)
- Scala 中的 Map 体系有一个 SortedMap,说明 Scala 的 Map 可以支持排序
- IndexedSeq 和 LinearSeq 的区别:
- IndexedSeq 是通过索引来查找和定位,因此速度快,比如 String 就是一个索引集合,通过索引即可定位
- LinearSeq 是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找
2.可变集合继承图
数组
1.不可变数组
- 定义数组(方式一)
- new 是关键字
- [Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定 Any
- (10),表示数组的大小,确定后就不可以变化
1 | val arr1 = new Array[Int](10) |
- 演示
1 | object TestArray{ |
1 | 4 |
- 定义数组(方式二)
- 在定义数组时,直接赋初始值
- 使用 apply 方法创建数组对象
- 演示
1 | object TestArray{ |
1 | 3 |
2.可变数组
- 定义可变数组
- [Any]存放任意数据类型
- (3, 2, 5)初始化好的三个元素
- ArrayBuffer 需要引入 scala.collection.mutable.ArrayBuffer
1 | val arr01 = ArrayBuffer[Any](3, 2, 5) |
- 演示
- ArrayBuffer 是有序的集合
- 增加元素使用的是 append 方法(),支持可变参数
1 | import scala.collection.mutable.ArrayBuffer |
1 | 1 |
3.不可变数组与可变数组的转换
- 说明
- arr2.toArray 返回结果才是一个不可变数组,arr2 本身没有变化
- arr1.toBuffer 返回结果才是一个可变数组,arr1 本身没有变化
1 | arr1.toBuffer //不可变数组转可变数组 |
- 演示
1 | object TestArrayBuffer { |
1 | ArrayBuffer(1, 2, 3) |
4.多维数组
- 定义
- 二维数组中有三个一维数组,每个一维数组中有四个元素
1 | val arr = Array.ofDim[Double](3,4) |
- 演示
1 | object DimArray { |
1 | 0 0 0 0 |
列表List
1.不可变List
- 说明
- List 默认为不可变集合
- 数据有顺序,可重复
- 集合间合并:将一个整体拆成一个一个的个体,称为扁平化
- 演示
1 | object TestList { |
1 | 1 |
2.可变ListBuffer
- 演示
1 | import scala.collection.mutable.ListBuffer |
1 | 1 |
Set集合
默认情况下,Scala 使用的是不可变集合,如果要使用可变集合,需要引用scala.collection.mutable.Set 包
1.不可变Set
- 说明
- Set 默认是不可变集合,数据无序
- 数据不可重复
- 演示
1 | object TestSet { |
1 | 5 |
2.可变 mutable.Set
- 演示
1 | object TestSet { |
1 | Set(9, 1, 5, 2, 6, 3, 4, 8) |
Map集合
Scala 中的 Map 和 Java 类似,也是一个散列表,它存储的内容也是键值对(key-value)映射
1.不可变Map
- 说明
- 如果 key 不存在,返回 0
- 演示
1 | object TestMap { |
1 | a=1 |
2.可变Map
- 演示
1 | object TestSet { |
1 | 1 |
元组
- 说明
- 元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组
- 演示
- Map 中的键值对其实就是元组,只不过元组的元素个数为 2,称之为对偶
1 | object TestTuple { |
1 | 40 |
集合常用函数
1.基本属性和常用操作
- 需求
- 获取集合长度
- 获取集合大小
- 循环遍历
- 迭代器
- 生成字符串
- 是否包含
- 演示
1 | object TestList { |
2.衍生集合
- 需求
- 获取集合的头
- 获取集合的尾(不是头的就是尾)
- 集合最后一个数据
- 集合初始数据(不包含最后一个)
- 反转
- 取前(后)n 个元素
- 去掉前(后)n 个元素
- 并集
- 交集
- 差集
- 拉链
- 滑窗
- 演示
1 | object TestList { |
1 | 1 |
3.集合计算简单函数
- 需求
- 求和
- 求乘积
- 最大值
- 最小值
- 排序
- 演示
1 | object TestList { |
1 | 8 |
- sorted
- 对一个集合进行自然排序,通过传递隐式的 Ordering
- sortBy
- 对一个属性或多个属性进行排序,通过它的类型
- sortWith
- 基于函数的排序,通过一个 comparator 函数,实现自定义排序的逻辑
4.集合计算高级函数
- 需求
- 过滤
- 遍历一个集合并从中获取满足指定条件的元素组成一个新的集合
- 转化/映射(map)
- 将集合中的每一个元素映射到某一个函数
- 扁平化
- 扁平化+映射 (flatMap 相当于先进行 map 操作,在进行 flatten 操作集合中的每个元素的子元素映射到某个函数并返回新集合)
- 分组(group)
- 按照指定的规则对集合的元素进行分组
- 简化(归约)
- 折叠
- 过滤
- 演示
1 | object TestList { |
1 | List(2, 4, 6, 8) |
- Reduce 方法
- Reduce 简化(归约) :通过指定的逻辑将集合中的数据进行聚合,从而减少数据,最终获取结果
- 演示
1 | object TestReduce { |
1 | i = -8 |
- Fold 方法
- Fold 折叠:化简的一种特殊情况
- 演示1
- fold 基本使用
1 | object TestFold { |
1 | -9 |
- 演示2
- 两个集合合并
1 | import scala.collection.mutable |
1 | Map(b -> 7, d -> 6, a -> 5, c -> 3) |
5.普通wordcount案例
- 需求
- 单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果
1 | object TestWordCount { |
1 | List((Hello,4), (Scala,3), (Hbase,2)) |
6.复杂wordcount案例
- 方式一(不通用)
1 | object TestWordCount { |
1 | List((Hello,10), (Scala,9), (Spark,7)) |
- 方式二
1 | object TestWordCount { |
8.队列
- 说明
- Scala 也提供了队列(Queue)的数据结构,队列的特点就是先进先出。进队和出队的方法分别为 enqueue 和 dequeue
- 演示
1 | import scala.collection.mutable |
1 | a |
9.并行集合
- 说明
- Scala 为了充分使用多核 CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算
- 演示
1 | object TestPar { |
1 | Vector(main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main, main) |
八、模式匹配
Scala 中的模式匹配类似于 Java 中的 switch 语法,补充了更多的功能
1 | int i = 10 |
基本语法
模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有 case 都不匹配,那么会执行 case _分支,类似于 Java 中 default 语句
1 | object TestMatchCase { |
1 | illegal |
- 说明
- 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句,若此时没有 case _ 分支,那么会抛出 MatchError
- 每个 case 中,不需要使用 break 语句,自动中断 case
- match case 语句可以匹配任何类型,而不只是字面量
- => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括
模式守卫
- 说明
- 如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫
- 演示
1 | object TestMatchGuard { |
1 | 5 |
模式匹配类型
1.匹配常量
- 说明
- Scala 中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等
- 演示
1 | object TestMatchVal { |
1 | String hello |
2.匹配类型
- 说明
- 需要进行类型判断时,可以使用前文所学的 isInstanceOf[T]和 asInstanceOf[T],也可使用模式匹配实现同样的功能
- 演示
1 | object TestMatchClass { |
1 | List |
3.匹配数组
- 说明
- scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为 0 的数组
- 演示
1 | object TestMatchArray { |
1 | result = 0 |
4.匹配列表
- 方式一
1 | object TestMatchList { |
1 | 0 |
- 方式二
1 | object TestMatchList { |
1 | 1-2-List(5, 6, 7) |
5.匹配元组
- 演示
1 | object TestMatchTuple { |
1 | 0 ... |
- 扩展案例
1 | object TestGeneric { |
1 | a |
6.匹配对象及样例类
- 基本语法
1 | class User(val name: String, val age: Int) |
1 | yes |
- 小结
- val user = User(“zhangsan”,11),该语句在执行时,实际调用的是 User 伴生对象中的apply 方法,因此不用 new 关键字就能构造出相应的对象
- 当将 User(“zhangsan”, 11)写在 case 后时[case User(“zhangsan”, 11) => “yes”],会默认调用 unapply 方法(对象提取器),user 作为 unapply 方法的参数,unapply 方法将 user 对象的 name 和 age 属性提取出来,与 User(“zhangsan”, 11)中的属性值进行匹配
- case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功,属性不一致,或返回 None,则匹配失败
- 若只提取对象的一个属性,则提取器为 unapply(obj:Obj):Option[T]
若提取对象的多个属性,则提取器为 unapply(obj:Obj):Option[(T1,T2,T3…)]
若提取对象的可变个属性,则提取器为 unapplySeq(obj:Obj):Option[Seq[T]]
- 样例类
- 语法
1 | case class Person (name: String, age: Int) |
- 说明
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中
自动提供了一些常用的方法,如 apply、unapply、toString、equals、hashCode 和 copy - 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例
类可以直接使用模式匹配,而无需自己实现 unapply 方法 - 构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中
- 演示
- 上述匹配对象的案例使用样例类会节省大量代码
1 | case class User(name: String, age: Int) |
1 | yes |
4.变量声明中的模式匹配
- 演示
1 | case class Person(name: String, age: Int) |
1 | x=1,y=2 |
5.for 表达式中的模式匹配
1 | object TestMatchFor { |
1 | A -> 1 |
6.偏函数中的模式匹配
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为 List[Int],而我们需要的是第一个元素是 0 的集合,这就是通过模式匹配实现的
- 偏函数定义
1 | val second: PartialFunction[List[Int], Option[Int]] = { |
注:该偏函数的功能是返回输入的 List 集合的第二个元素
- 偏函数原理
- 上述代码会被 scala 编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数检查的函数——isDefinedAt,其返回值类型为 Boolean
1 | val second = new PartialFunction[List[Int], Option[Int]] { |
- 偏函数使用
- 偏函数不能像 second(List(1,2,3))这样直接使用,因为这样会直接调用 apply 方法,而应该调用 applyOrElse 方法,如下面的代码
- applyOrElse 方法的逻辑为 if (ifDefinedAt(list)) apply(list) else default。如果输入参数满足条件,即 isDefinedAt 返回 true,则执行 apply 方法,否则执行 defalut 方法,default 方法为参数不满足要求的处理逻辑
1 | second.applyOrElse(List(1,2,3), (_: List[Int]) => None) |
- 案例
- 将 List(1,2,3,4,5,6,“test”)中的 Int 类型的元素加一,并去掉字符串
1 | def main(args: Array[String]): Unit = { |
1 | List(2, 3, 4, 5, 6, 7) |
- 偏函数实现
- 方法一
1 | def main(args: Array[String]): Unit = { |
1 | 2 |
- 方法二
1 | def main(args: Array[String]): Unit = { |
1 | 2 |
九、异常
Java异常处理
1 | public class ExceptionDemo { |
- 注意事项
- Java 语言按照 try—catch—finally 的方式来处理异常
- 不管有没有异常捕获,都会执行 finally,因此通常可以在 finally 代码块中释放资
源 - 可以有多个 catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误
Scala异常处理
1 | def main(args: Array[String]): Unit = { |
- 我们将可疑代码封装在 try 块中。在 try 块之后使用了一个 catch 处理程序来捕获异
常;如果发生任何异常,catch 处理程序将处理它,程序将不会异常终止 - Scala 的异常的工作机制和 Java 一样,但是 Scala 没有“checked(编译期)”异常, 即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理
- 异常捕捉的机制与其他语言中一样,如果有异常发生,catch 子句是按次序捕捉的。
因此,在 catch 子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在 Scala 中也不会报错,但这样是非常不好的编程风格 - finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用
于对象的清理工作,这点和 Java 一样 - 用 throw 关键字,抛出一个异常对象。所有异常都是 Throwable 的子类型。throw 表达式是有类型的,就是 Nothing,因为 Nothing 是所有类型的子类型,所以 throw 表达式可以用在需要类型的地方
1 | def test():Nothing = { |
- java 提供了 throws 关键字来声明异常。可以使用方法定义声明异常。它向调用者函
数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在 try-catch块中,以避免程序异常终止。在 Scala 中,可以使用 throws 注解来声明异常
1 | def main(args: Array[String]): Unit = { |
十、隐式转换
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译
隐式函数
- 说明
- 隐式转换可以在不需改任何代码的情况下,扩展某个类的功能
- 演示
- 通过隐式转化为 Int 类型增加方法
1 | class MyRichInt(val self: Int) { |
1 | 6 |
隐式参数
普通方法或者函数中的参数可以通过 implicit 关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值
- 说明
- 同一个作用域中,相同类型的隐式值只能有一个
- 编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关
- 隐式参数优先于默认参数
- 案例
1 | object TestImplicitParameter { |
1 | hello world! |
隐式类
在 Scala2.10 后提供了隐式类,可以使用 implicit 声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用
- 说明
- 其所带的构造参数有且只能有一个
- 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是 顶级的
- 案例
1 | object TestImplicitClass { |
1 | 3 |
隐式解析机制
- 说明
- 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象) (一般情况下)
- 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象 以及该类型所在包的包对象
- 案例
1 | import com.atguigu.chapter10.Scala05_Transform4.Teacher |
十一、泛型
协变和逆变
- 语法
1 | class MyList[+T]{ //协变 |
- 说明
- 协变:Son 是 Father 的子类,则 MyList[Son] 也作为 MyList[Father]的“子类”
- 逆变:Son 是 Father 的子类,则 MyList[Son]作为 MyList[Father]的“父类”
- 不变:Son 是 Father 的子类,则 MyList[Father]与 MyList[Son]“无父子关系”
- 演示
1 | //泛型模板 |
泛型上下限
- 语法
1 | Class PersonList[T <: Person]{ //泛型上限 |
- 说明
- 泛型的上下限的作用是对传入的泛型进行限定
- 演示
1 | class Parent{} |
上下文限定
- 语法
1 | def f[A : B](a: A) = println(a) //等同于 def f[A](a:A)(implicit arg:B[A])=println(a) |
- 说明
- 上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]]获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误
1 | implicit val x = 1 |
- 演示
1 | def f[A:Ordering](a:A,b:A) =implicitly[Ordering[A]].compare(a,b) |
十二、IDEA快捷键
- 快速生成程序入口:main
1 | 输入 main->回车 |
- 自动补全变量:.var
1 | 输入 1.var->回车 |
- 快速打印:.sout
1 | 输入 1.sout->回车 |
- 快速生成 for 循环:遍历对象.for
1 | 输入 1 to 3.for |
- 查看当前文件的结构:Ctrl + F12
- 格式化当前代码:Ctrl + Shift + L
- 自动为当前代码补全变量声明:Ctrl + Shift + V