【学习笔记】大数据技术之Scala(下)
尚硅谷大数据技术之Scala(新版)
- 思考:
- 1. 为什么scala在生成字节码文件的过程中,会有 类名$.class文件生成?
- 2. 闭包时如何实现的?
- 第 1 章 Scala 入门
- 1.1 概述
- 1.1.1 为什么学习 Scala
- 1.1.2 Scala 发展历史
- 1.1.3 Scala 和 Java 关系
- 1.1.4 Scala 语言特点
- 1.2 Scala 环境搭建
- 1.3 Scala 插件安装
- 1.4 HelloWorld 案例
- 1.4.1 创建 IDEA 项目工程
- 1.4.2 class 和 object 说明
- 1.4.3 Scala 程序反编译
- 1.5 关联 Scala 源码
- 第 2 章 变量和数据类型
- 2.1 注释
- 2.2 变量和常量(重点)
- 2.3 标识符的命名规范
- 2.4 字符串输出
- 2.5 键盘输入 和输出
- 2.6 数据类型(重点)
- 2.7 整数类型(Byte、Short、Int、Long)
- 2.8 浮点类型(Float、Double)
- 2.9 字符类型(Char)
- 2.10 布尔类型:Boolean
- 2.11 Unit 类型、Null 类型和 Nothing 类型(重点)
- 2.12 类型转换
- 2.12.1 数值类型自动转换
- 2.12.2 强制类型转换
- 2.12.3 数值类型和 String 类型间转换
- 第 3 章 运算符
- 3.1 算术运算符
- 3.2 关系运算符(比较运算符)
- 3.3 逻辑运算符
- 3.4 赋值运算符
- 3.5 位运算符
- 3.6 Scala 运算符本质
- 第 4 章 流程控制
- 4.1 分支控制 if-else
- 4.1.1 单分支
- 4.1.2 双分支
- 4.1.3 多分支
- 4.2 嵌套分支
- 4.3 Switch 分支结构
- 4.4 For 循环控制
- 4.4.1 范围数据循环(To)
- 4.4. 集合遍历
- 4.4.2 范围数据循环(Until)
- 4.4.3 循环守卫
- 4.4.4 循环步长
- 4.4.5 嵌套循环
- 4.4.6 引入变量
- 4.4.7 循环返回值
- 4.4.8 倒序打印
- 4.5 While 和 do..While 循环控制
- 4.5.1 While 循环控制
- 4.5.2 do..while 循环控制
- 4.6 循环中断
- 4.7 多重循环
- 第 5 章 函数式编程
- 5.1 函数基础
- 5.1.1 函数基本语法
- 5.1.2 函数和方法的区别
- 5.1.3 函数定义
- 5.1.4 函数参数
- 5.1.5 函数至简原则(重点)
- 5.2 函数高级
- 5.2.1 高阶函数
- 5.2.2 匿名函数
- 5.2.3 高阶函数案例
- 5.2.4 函数柯里化&闭包
- 5.2.5 递归/尾递归
- 5.2.6 控制抽象
- 5.2.7 惰性加载
尚硅谷大数据技术之Scala(新版)
思考:
1. 为什么scala在生成字节码文件的过程中,会有 类名$.class文件生成?
涉及到底层。
.class文件问调用$.class文件里面的方法(构造方法私有化,然后在定义一个静态对象)(单例设计模式)
Object相当于声明个对象。
详细讲解
2. 闭包时如何实现的?
调用函数(jvm虚拟机栈里的栈帧,func1调用,func1弹出,func2调用,func2弹出)时,相当于调用对象实例(保存在jvn虚拟机堆里),然后在对象实例里,把相关的,它所依赖的外部环境和局部变量打包保存在对象实例
第 1 章 Scala 入门
1.1 概述
1.1.1 为什么学习 Scala
为什么学习Scala
1)Spark—新一代内存级大数据计算框架,是大数据的重要内容。
2)Spark就是使用Scala编写的。因此为了更好的学习Spark, 需要掌握Scala这门语言。
3)Spark的兴起,带动Scala语言的发展!
Scala - Java++
1)Scala基于JVM,和Java完全兼容,同样具有跨平台、可移植性好、方便的垃圾回收等特性
2)Scala 比 Java 更加面向对象
3)Scala是一门函数式编程语言
1.1.2 Scala 发展历史
Scala发展历史
联邦理工学院的马丁·奥德斯基(Martin Odersky)于2001年开始设计Scala。
马丁·奥德斯基是编译器及编程的狂热爱好者,长时间的编程之后,希望发明一种语言,能够让写程序这样的基础工作变得高效,简单。所以当接触到JAVA语言后,对JAVA这门便携式,运行在网络,且存在垃圾回收的语言产生了极大的兴趣,所以决定将函数式编程语言的特点融合到JAVA中,由此发明了两种语言(Pizza & Scala)。
Pizza和Scala极大地推动了Java编程语言的发展。
⚫ JDK5.0 的泛型、增 强for循环、自动类型转换等,都是从Pizza引入的新特性。
⚫ JDK8.0 的类型推断、Lambda表达式就是从Scala引入的特性。
JDK5.0和JDK8.0的编辑器就是马丁·奥德斯基写的,因此马丁·奥德斯基一个人的战斗力抵得上一个Java开发团队。
1.1.3 Scala 和 Java 关系
一般来说,学 Scala 的人,都会 Java,而 Scala 是基于 Java 的,因此我们需要将 Scala和 Java 以及 JVM 之间的关系搞清楚,否则学习 Scala 你会蒙圈。
1.1.4 Scala 语言特点
Scala是一门以Java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言(静态语言需要提前编译的如:Java、c、c++等,动态语言如:js)。
1)Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程。(多范式,就是多种编程方法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法。)
2)Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接。
3)Scala单作为一门语言来看,非常的简洁高效。
4)Scala在设计时,马丁·奥德斯基是参考了Java的设计思想,可以说Scala是源于Java,同时马丁·奥德斯基也加入了自己的思想,将函数式编程语言的特点融合到JAVA中, 因此,对于学习过Java的同学,只要在学习Scala的过程中,搞清楚Scala和Java相同点和不同点,就可以快速的掌握Scala这门语言。
1.2 Scala 环境搭建
1)安装步骤
(1)首先确保 JDK1.8 安装成功
(2)下载对应的 Scala 安装文件 scala-2.12.11.zip
(3)解压 scala-2.12.11.zip,我这里解压到 D:\Tools
(4)配置 Scala 的环境变量,path
注意 1:解压路径不能有任何中文路径,最好不要有空格。
注意 2:环境变量要大写 SCALA_HOME
2)测试
需求:计算两数 a 和 b 的和。
步骤
(1)在键盘上同时按 win+r 键,并在运行窗口输入 cmd 命令
(2)输入 Scala 并按回车键,启动 Scala 环境。然后定义两个变量,并计算求和。
1.3 Scala 插件安装
默认情况下 IDEA 不支持 Scala 的开发,需要安装 Scala 插件。
1)插件离线安装步骤
(1)建议将该插件 scala-intellij-bin-2017.2.6.zip 文件,放到 Scala 的安装目录D:\Tools\scala-2.12.11 下,方便管理。
(2)打开 IDEA,在左上角找到 File->在下拉菜单中点击 Setting… ->点击 Plugins->点击右 下 角 Install plugin from disk… , 找 到 插 件 存 储 路 径
D:\Tools\scala-2.12.11\scala-intellij-bin-2017.2.6.zip,最后点击 ok。
2)插件在线安装(可选)
(1)在搜索插件框里面输入 Scala->点击 Install->点击 ok->点击 apply。
(2)重启 IDEA,再次来到 Scala 插件页面,已经变成 Uninstall。
1.4 HelloWorld 案例
1.4.1 创建 IDEA 项目工程
1)打开 IDEA->点击左侧的 Flie->选择 New->选择 Project…
2)创建一个 Maven 工程,并点击 next
3)GroupId 输入 com.atguigu->ArtifactId 输入 scala->点击 next->点击 Finish
注意:工程存储路径一定不要有中文和空格。
4)指定项目工作目录空间
5)默认下,Maven 不支持 Scala 的开发,需要引入 Scala 框架。
在 scala0513 项目上,点击右键-> Add Framework Support… ->选择 Scala->点击 OK
注意:如果是第一次引入框架,Use libary 看不到,需要选择你的 Scala 安装目录,然后工具就会自动识别,就会显示 user libary。
6)创建项目的源文件目录
右键点击 main 目录->New->点击 Diretory -> 写个名字(比如 scala)。
右键点击 scala 目录->Mark Directory as->选择 Sources root,观察文件夹颜色发生变化。
7)在 scala 包下,创建包 com.atguigu.chapter01 包名和 Hello 类名,
右键点击 scala 目录->New->Package->输入 com.atguigu.chapter01->点击 OK。
右键点击 com.atguigu.chapter01->New->Scala Class->Kind 项选择 Object->Name 项输入
Hello。
8)编写输出 Hello Scala 案例
package chapter01
/**
* object: 关键字,声明一个单例对象(伴生对象)
*/
object HelloWorld {
/*
main 方法: 从外部可以执行调用执行的方法
def 方法名称(参数名称:参数类型):返回值类型 = { 方法体 }
*/
def main(args: Array[String]): Unit = {
// string(泛型)类型的数组
println("hello world")
System.out.println("heleo scalla rom hava")
}
}
在类中中输入 main,然后回车可以快速生成 main 方法;
在 main 方法中输入 println(“hello scala”)
运行后,观察控制台打印输出:
hello scala
hello scala
说明:Java 中部分代码也是可以在 Scala 中运行。
1.4.2 class 和 object 说明
单例对象(伴生对象)也就是object关键字,就是为了解决java中的static关键字静态功能提出来的。
object本身就是一个对象,所有功能都是基于它来做调用。(所以就完全的面对对象了)
Scala是一个完全面向对象的语言,所以没有静态语法,为了能调用静态语法(模仿静态语法),采用伴生对象单例的方式调用方法
在scala如何实现静态属性school的调用呢?
package chapter01
// 入口类 + 伴生类
// 属性像参数一样传进去,连构造方法都定义好了,构造方法不用写
class Student(name:String, age:Int) {
// 定义一个方法
def printlnfo():Unit = {
println(name + " " + age + " " + Student.school)
}
}
// 引入伴生对象, 解决静态属性,school调用问题 会放在 $.class文件里面
// 伴生对象里面所有的私有成员都是可以互相访问的
object Student{
// 将全局只有一份的属性直接放到 全局只有一份的伴生对象里面
val school:String = "atguigu"
def main(args: Array[String]): Unit = {
val alice = new Student("alice", 20)
val bob = new Student("bob", 23)
alice.printlnfo()
bob.printlnfo()
}
}
Scala完全面向对象,故Scala去掉了Java中非面向对象的元素,如static关键字,void类型
1)static
Scala无static关键字,由object实现类似静态方法的功能(类名.方法名)。
class关键字和Java中的class关键字作用相同,用来定义一个类;
2)void
对于无返回值的函数,Scala定义其返回值类型为Unit类
1.4.3 Scala 程序反编译
1)在项目的 target 目录 Hello 文件上点击右键->Show in Explorer->看到 object 底层生成Hello$.class
和Hello.class
两个文件
2)采用 Java 反编译工具 jd-gui.exe 反编译代码,将 Hello.class 拖到 jd-gui.exe 页面
1.5 关联 Scala 源码
在使用 Scala 过程中,为了搞清楚 Scala 底层的机制,需要查看源码,下面看看如何关联和查看 Scala 的源码包。
1)查看源码
例如查看 Array 源码。按住 ctrl 键->点击 Array->右上角出现 Attach Soures…
2)关联源码
(1)将我们的源码包 scala-sources-2.12.11.tar.gz 拷贝到 D:\Tools\scala-2.12.11\lib 文件夹
下,并解压为 scala-sources-2.12.11 文件夹
(2)点击 Attach Sources…->选择 D:\Tools\scala-2.12.11\lib\scala-sources-2.12.11,这个
文件夹,就可以看到源码了
1.6 官方编程指南
1)在线查看:https://www.scala-lang/
2)离线查看:解压 scala-docs-2.11.8.zip,可以获得 Scala 的 API 操作。
第 2 章 变量和数据类型
2.1 注释
Scala 注释使用和 Java 完全一样。
注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现。
1)基本语法
(1)单行注释://
(2)多行注释:/* */
(3)文档注释:
/**
*
*/
2)案例实操
package chapter02
/*
多行注释
这是一个简单的测试程序
*/
object Test01_Comment {
/**
* 可以直接生成文档
* 这是程序的入口方法
* @param args 外部传入的参数
*/
def main(args: Array[String]): Unit = {
// 打印输出
println("hello")
}
}
3) 代码规范
(1)使用一次 tab 操作,实现缩进,默认整体向右边移动,用 shift+tab 整体向左移
(2)或者使用 ctrl + alt + L 来进行格式化
(3)运算符两边习惯性各加一个空格。比如:2 + 4 * 5。
(4)一行最长不超过 80 个字符,超过的请使用换行展示,尽量保持格式优雅
2.2 变量和常量(重点)
常量:在程序执行的过程中,其值不会被改变的变量
0)回顾:Java 变量和常量语法
变量类型 变量名称 = 初始值 int a = 10
final 常量类型 常量名称 = 初始值 final int b = 20
1)基本语法
var 变量名 [: 变量类型] = 初始值 var i:Int = 10
val 常量名 [: 常量类型] = 初始值 val j:Int = 20
注意:能用常量的地方不用变量
2)案例实操
(1)声明变量时,类型可以省略,编译器自动推导,即类型推导
(2)类型确定后,就不能修改,说明 Scala 是强数据类型语言。
(3)变量声明时,必须要有初始值
(4)在声明/定义一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变,val 修饰的变量不可改。
2.3 标识符的命名规范
Scala 对各种变量、方法、函数等命名时使用的字符序列称为标识符。即:凡是自己可以起名字的地方都叫标识符。
1)命名规则
Scala 中的标识符声明,基本和 Java 是一致的,但是细节上会有所变化,有以下三种规则:
(1)以字母或者下划线开头,后接字母、数字、下划线
(2)以操作符开头,且只包含操作符(+ - * / # !等)
(3)用反引号....
包括的任意字符串,即使是 Scala 关键字(39 个)也可以
• package, import, class, object, trait, extends, with, type, for
• 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
2)案例实操
需求:判断 hello、Hello12、1hello、h-b、x h、h_4、ab、Int、、+-/#!、+-/#!1、if、if
,这些名字是否合法。
2.4 字符串输出
1)基本语法
(1)字符串,通过+号连接
(2)printf 用法:字符串,通过%传值。
(3)字符串模板(插值字符串):通过$获取变量值
2)案例实操
package chapter02
object Test04_String {
def main(args: Array[String]): Unit = {
var name: String = "jinlian"
var age: Int = 18
//(1)字符串,通过+号连接
println(name + " " + age)
println(name * 8)
//(2)printf 用法字符串,通过%传值。
printf("name=%s age=%d\n", name, age)
//(3)字符串,通过$引用
//多行字符串,在 Scala中,利用三个双引号包围多行字符串就可以实现。
//输入的内容,带有空格、\t 之类,导致每一行的开始位置不能整洁对齐。
//应用 scala 的 stripMargin 方法,在 scala 中 stripMargin 默认 是“|”作为连接符,//在多行换行的行头前面加一个“|”符号即可。
val s =
"""
|select
| name,
| age
|from user
|where name="zhangsan"
""".stripMargin
println(s)
//如果需要对变量进行运算,那么可以加${}
val s1 =
s"""
|select
| name,
| age
|from user
|where name="$name" and age=${age+2}
""".stripMargin
println(s1)
val s2 = s"name=$name"
println(s2)
val num: Double = 2.3456
println(f"The num is ${num}%2.2f") // 格式化目标字符串
println(raw"The num is ${num}%2.2f")
}
}
2.5 键盘输入 和输出
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。
1)基本语法
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
2)案例实操
需求:可以从控制台接收用户信息,【姓名,年龄,薪水】。
package chapter02
import scala.io.StdIn
object Test05_StdIn {
def main(args: Array[String]): Unit = {
// 1 输入姓名
println("input name:")
var name = StdIn.readLine()
// 2 输入年龄
println("input age:")
var age = StdIn.readShort()
// 3 输入薪水
println("input sal:")
var sal = StdIn.readDouble()
// 4 打印
println("name=" + name)
println("age=" + age)
println("sal=" + sal)
}
}
从文件读取数据
package chapter02
import java.io.{File, PrintWriter}
import scala.io.Source
object Test06_FileO {
def main(args: Array[String]): Unit = {
// 1. 从文件中读取数据
Source.fromFile("src\\main\\resources\\test.txt").foreach(print)
// 2. 将数据写入文件
val writer = new PrintWriter(new File("src\\main\\resources\\output.txt"))
writer.write("hello scala from java")
writer.close()
}
}
2.6 数据类型(重点)
回顾:Java数据类型
Java基本类型:char、byte、short、int、long、float、double、boolean
Java引用类型:(对象类型)
由于Java有基本类型,而且基本类型不是真正意义的对象,即使后面产生了基本类型的包装类,但是仍然存在基本数据类型,所以Java语言并不是真正意思的面向对象。
Java基本类型的包装类:Character、Byte、Short、Integer、Long、Float、Double、Boolean
注意:Java中基本类型和引用类型没有共同的祖先。
2.7 整数类型(Byte、Short、Int、Long)
Scala 的整数类型就是用于存放整数值的,比如 12,30,3456 等等。
1)整型分类
第一位为符号数
0000 0000 0
1000 0000 -128
2^10 = 1K
2^20 = 1M
2^30 = 1G
2)案例实操
(1)Scala 各整数类型有固定的表示范围和字段长度,不受具体操作的影响,以保证Scala 程序的可移植性。
整数默认类型为Int
object TestDataType {
def main(args: Array[String]): Unit = {
// 正确
var n1:Byte = 127
var n2:Byte = -128
// 错误
// var n3:Byte = 128
// var n4:Byte = -129
}
}
(2)Scala 的整型,默认为 Int 型,声明 Long 型,须后加‘l’或‘L’
object TestDataType {
def main(args: Array[String]): Unit = {
var n5 = 10
println(n5)
var n6 = 9223372036854775807L
println(n6)
}
}
(3)Scala 程序中变量常声明为 Int 型,除非不足以表示大数,才使用 Long
2.8 浮点类型(Float、Double)
Scala 的浮点类型可以表示一个小数,比如 123.4f,7.8,0.12 等等。
1)浮点型分类
2)案例实操
Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加‘f’或‘F’。
object TestDataType {
def main(args: Array[String]): Unit = {
// 建议,在开发中需要高精度小数时,请选择 Double
var n7 = 2.2345678912f
var n8 = 2.2345678912
println("n7=" + n7)
println("n8=" + n8)
}
}
//运行的结果
n7=2.2345679
n8=2.2345678912
2.9 字符类型(Char)
1)基本说明
字符类型可以表示单个字符,字符类型是 Char。
2)案例实操
(1)字符常量是用单引号 ’ ’ 括起来的单个字符。
(2)\t :一个制表位,实现对齐的功能
(3)\n :换行符
(4)\ :表示
(5)" :表示"
object TestCharType {
def main(args: Array[String]): Unit = {
//(1)字符常量是用单引号 ' ' 括起来的单个字符。
var c1: Char = 'a'
println("c1=" + c1)
//注意:这里涉及自动类型提升,其实编译器可以自定判断是否超出范围,
//不过 idea 提示报错
var c2:Char = 'a' + 1
println(c2)
//(2)\t :一个制表位,实现对齐的功能
println("姓名\t 年龄")
//(3)\n :换行符
println("西门庆\n 潘金莲")
//(4)\\ :表示\
println("c:\\岛国\\avi")
//(5)\" :表示"
println("同学们都说:\"大海哥最帅\"")
}
}
2.10 布尔类型:Boolean
1)基本说明
(1)布尔类型也叫 Boolean 类型,Booolean 类型数据只允许取值 true 和 false
(2)boolean 类型占 1 个字节。
2)案例实操
object TestBooleanType {
def main(args: Array[String]): Unit = {
var isResult : Boolean = false
var isResult2 : Boolean = true
}
}
2.11 Unit 类型、Null 类型和 Nothing 类型(重点)
1)基本说明
2)案例实操
(1)Unit 类型用来标识过程,也就是没有明确返回值的函数。
由此可见,Unit 类似于 Java 里的 void。Unit 只有一个实例——( ),这个实例也没有实质意义
// 空值 Unit
def m1():Unit={// unit 表示没有返回值,即 void
print("m1被调用执行")
}
val a :Unit= m1()
println(a)
(2)Null 类只有一个实例对象,Null 类似于 Java 中的 null 引用。Null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
object TestDataType {
def main(args: Array[String]): Unit = {
//null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
var cat = new Cat();
cat = null // 正确
var n1: Int = null // 错误
println("n1:" + n1)
}
}
class Cat {
}
(3)Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于 Nothing 是其他任意类型的子类,他还能跟要求返回值的方法兼容。
object TestSpecialType {
def main(args: Array[String]): Unit = {
def test() : Nothing={
throw new Exception()
}
test
}
}
2.12 类型转换
扩展 Java 面试题(隐式类型转换):
2.12.1 数值类型自动转换
当 Scala 程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)。数据类型按精度(容量)大小排序为:
1)基本说明
(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
(2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
(3)(byte,short)和 char 之间不会相互自动转换。
(4)byte,short,char 他们三者可以计算,在计算时首先转换为 int 类型。
2)案例实操
object TestValueTransfer {
def main(args: Array[String]): Unit = {
//(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数值类型,然后再进行计算。
var n = 1 + 2.0
println(n) // n 就是 Double
//(2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
var n2 : Double= 1.0
//var n3 : Int = n2 //错误,原因不能把高精度的数据直接赋值和低精度。
//(3)(byte,short)和 char 之间不会相互自动转换。
var n4 : Byte = 1
//var c1 : Char = n4 //错误
var n5:Int = n4
//(4)byte,short,char 他们三者可以计算,在计算时首先转换为 int类型。
var n6 : Byte = 1
var c2 : Char = 1
// var n : Short = n6 + c2 //当 n6 + c2 结果类型就是 int
// var n7 : Short = 10 + 90 //错误
}
}
注意:Scala 还提供了非常强大的隐式转换机制(隐式函数,隐式类等),我们放在高级部分专门用一个章节来讲解。
2.12.2 强制类型转换
1)基本说明
自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。
Java : int num = (int)2.5
Scala : var num : Int = 2.7.toInt
2)案例实操
(1)将数据由高精度转换为低精度,就需要使用到强制转换
(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
object TestForceTransfer {
def main(args: Array[String]): Unit = {
//(1)将数据由高精度转换为低精度,就需要使用到强制转换
var n1: Int = 2.5.toInt // 这个存在精度损失
//(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
var r1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt // 10 *3 + 6*1 = 36
var r2: Int = (10 * 3.5 + 6 * 1.5).toInt // 44.0.toInt = 44
println("r1=" + r1 + " r2=" + r2)
}
}
2.12.3 数值类型和 String 类型间转换
1)基本说明
在程序开发中,我们经常需要将基本数值类型转成 String 类型。或者将 String 类型转成基本数值类型。
2)案例实操
(1)基本类型转 String 类型(语法:将基本类型的值+"" 即可)
(2)String 类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
object TestStringTransfer {
def main(args: Array[String]): Unit = {
//(1)基本类型转 String 类型(语法:将基本类型的值+"" 即可)
var str1 : String = true + ""
var str2 : String = 4.5 + ""
var str3 : String = 100 +""
//(2)String 类型转基本数值类型(语法:调用相关 API)
var s1 : String = "12"
var n1 : Byte = s1.toByte
var n2 : Short = s1.toShort
var n3 : Int = s1.toInt
var n4 : Long = s1.toLong
}
}
(3)注意事项
在将 String 类型转成基本数值类型时,要确保 String 类型能够转成有效的数据,比如我们可以把"123",转成一个整数,但是不能把"hello"转成一个整数。
var n5:Int = “12.6”.toInt 会出现 NumberFormatException 异常。
扩展面试题
二进制的原码、反码、补码
128: int类型,占据四个字节,32位
原码: 00000000 00000000 00000000 10000000
补码:00000000 00000000 00000000 10000000
截取最后一个字节,Byte,
得到补码 1000 0000
表示最大负数 -128
130: int类型,占据四个字节,32位
原码: 00000000 00000000 00000000 10000010
补码:00000000 00000000 00000000 10000010
截取最后一个字节,Byte,
得到补码 1000 0010
对应的原码 1111 1101 + 1 = 1111 1110
-126
第 3 章 运算符
Scala 运算符的使用和 Java 运算符的使用基本相同,只有个别细节上不同。
3.1 算术运算符
1)基本语法
(1)对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。
(2)对一个数取模 a%b,和 Java 的取模规则一样。
2)案例实操
object TestArithmetic {
def main(args: Array[String]): Unit = {
//(1)对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法 时,只保留整数部分而舍弃小数部分。
var r1: Int = 10 / 3 // 3
println("r1=" + r1)
var r2: Double = 10 / 3 // 3.0
println("r2=" + r2)
var r3: Double = 10.0 / 3 // 3.3333
println("r3=" + r3)
println("r3=" + r3.formatted("%.2f")) // 含义:保留小数点 2位,使用四舍五入
//(2)对一个数取模 a%b,和 Java 的取模规则一样。
var r4 = 10 % 3 // 1
println("r4=" + r4)
}
}
3.2 关系运算符(比较运算符)
1)基本语法
2)案例实操
(1)需求 1:
object TestRelation {
def main(args: Array[String]): Unit = {
// 测试:>、>=、<=、<、==、!=
var a: Int = 2
var b: Int = 1
println(a > b) // true
println(a >= b) // true
println(a <= b) // false
println(a < b) // false
println("a==b" + (a == b)) // false
println(a != b) // true
}
}
(2)需求 2:Java 和 Scala 中关于==
的区别
Java:
==
比较两个变量本身的值,即两个对象在内存中的首地址;
equals
比较字符串中所包含的内容是否相同。
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
输出结果:
false
true
Scala:==更加类似于 Java 中的 equals,参照 jd 工具
def main(args: Array[String]): Unit = {
val s1 = "abc"
val s2 = new String("abc")
println(s1 == s2) // 值比较
println(s1.equals(s2)) // 值比较
println(s1.eq(s2)) // 引用比较
}
输出结果:
true
true
false
3.3 逻辑运算符
1)基本语法
用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个 Boolean 值。
假定:变量 A 为 true,B 为 false
短路运算
2)案例实操
object TestLogic {
def main(args: Array[String]): Unit = {
// 测试:&&、||、!
var a = true
var b = false
println("a&&b=" + (a && b)) // a&&b=false
println("a||b=" + (a || b)) // a||b=true
println("!(a&&b)=" + (!(a && b))) // !(a&&b)=true
}
}
扩展避免逻辑与空指针异常 短路与
isNotEmpty(String s){
//如果按位与,s 为空,会发生空指针
return s!=null && !"".equals(s.trim());
}
3.4 赋值运算符
1)基本语法
赋值运算符就是将某个运算后的值,赋给指定的变量。
注意:Scala 中没有++、–操作符,可以通过+=、-=来实现同样的效果;
2)案例实操
object TestAssignment {
def main(args: Array[String]): Unit = {
var r1 = 10
r1 += 1 // 没有++
r1 -= 2 // 没有--
}
}
3.5 位运算符
1)基本语法
下表中变量 a 为 60,b 为 13。
2)案例实操
object TestPosition {
def main(args: Array[String]): Unit = {
// 测试:1000 << 1 =>10000
var n1 :Int =8
n1 = n1 << 1
println(n1)
}
}
3.6 Scala 运算符本质
在 Scala 中其实是没有运算符的,所有运算符都是方法。
1)当调用对象的方法时,点.
可以省略
2)如果函数参数只有一个,或者没有参数,()可以省略
object TestOpt {
def main(args: Array[String]): Unit = {
// 标准的加法运算
val i:Int = 1.+(1)
// (1)当调用对象的方法时,.可以省略
val j:Int = 1 + (1)
// (2)如果函数参数只有一个,或者没有参数,()可以省略
val k:Int = 1 + 1
println(1.toString())
println(1 toString())
println(1 toString)
}
}
第 4 章 流程控制
4.1 分支控制 if-else
让程序有选择的的执行,分支控制有三种:单分支、双分支、多分支
4.1.1 单分支
1)基本语法
if (条件表达式) {
执行代码块
}
说明:当条件表达式为 ture 时,就会执行{ }的代码。
2)案例实操
需求:输入人的年龄,如果该同志的年龄小于 18 岁,则输出“童年”
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age:")
var age = StdIn.readShort()
if (age < 18){
println("童年")
}
}
}
4.1.2 双分支
1)基本语法
if (条件表达式) {
执行代码块 1
} else {
执行代码块 2
}
2)案例实操
需求:输入年龄,如果年龄小于 18 岁,则输出“童年”。否则,输出“成年”。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age:")
var age = StdIn.readShort()
if (age < 18){
println("童年")
}else{
println("成年")
}
}
}
4.1.3 多分支
1)基本语法
if (条件表达式 1) {
执行代码块 1
}
else if (条件表达式 2) {
执行代码块 2
}
……
else {
执行代码块 n
}
2)案例实操
(1)需求 1:需求:输入年龄,如果年龄小于 18 岁,则输出“童年”。如果年龄大于等于 18 且小于等于 30,则输出“中年”,否则,输出“老年”。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
if (age < 18){
println("童年")
}else if(age>=18 && age<30){
println("中年")
}else{
println("老年")
}
}
}
(2)需求 2:Scala 中 if else 表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res :String = if (age < 18){
"童年"
}else if(age>=18 && age<30){
"中年"
}else{
"老年"
}
println(res)
}
}
输出为()
(3)需求 3:Scala 中返回值类型不一致,取它们共同的祖先类型any。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18){
"童年"
}else if(age>=18 && age<30){
"中年"
}else{
100
}
println(res)
}
}
(4)需求 4:Java 中的三元运算符可以用 if else 实现
如果大括号{}内的逻辑代码只有一行,大括号可以省略。如果省略大括号,if 只对最近的一行逻辑代码起作用。
object TestIfElse {
def main(args: Array[String]): Unit = {
// Java
// int result = flag?1:0
// Scala
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18) "童年" else "成年"
"不起作用"
println(res)
}
}
4.2 嵌套分支
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层。分支外面的分支结构称为外层分支。嵌套分支不要超过 3 层。
1)基本语法
if(){
if(){
}else{
}
}
2)案例实操
需求:如果输入的年龄小于 18,返回“童年”。如果输入的年龄大于等于 18,需要再判断:如果年龄大于等于 18 且小于 30,返回“中年”;如果其他,返回“老年”。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res :String = if (age < 18){
"童年"
}else {
if(age>=18 && age<30){
"中年"
}else{
"老年"
}
}
println(res)
}
}
4.3 Switch 分支结构
在 Scala 中没有 Switch,而是使用模式匹配来处理。
模式匹配涉及到的知识点较为综合,因此我们放在后面讲解。
4.4 For 循环控制
Scala 也为 for 循环这一常见的控制结构提供了非常多的特性,这些 for 循环的特性被称为 for 推导式或 for 表达式。
4.4.1 范围数据循环(To)
1)基本语法
for(i <- 1 to 3){
print(i + " ")
}
println()
(1)i 表示循环的变量,<- 规定 to
(2)i 将会从 1-3 循环,前后闭合
2)案例实操
需求:输出 5 句 “宋宋,告别海狗人参丸吧”
object TestFor {
def main(args: Array[String]): Unit = {
for(i <- 1 to 5){
println("宋宋,告别海狗人参丸吧"+i)
}
}
}
// Range为伴生对象
for(i <- Range(1,10)){
println(i + ".hello world")
}
for(i <- 1 until(10)){
println(i)
}
4.4. 集合遍历
for(i <- Array(12,34,52)){
println(i)
}
for(i <- Set(12,34,52)){
println(i)
}
for(i <- List(12,34,52)){
println(i)
}
4.4.2 范围数据循环(Until)
1)基本语法
for(i <- 1 until 3) {
print(i + " ")
}
println()
(1)这种方式和前面的区别在于 i 是从 1 到 3-1
(2)即使前闭合后开的范围
2)案例实操
需求:输出 5 句 “宋宋,告别海狗人参丸吧”
object TestFor {
def main(args: Array[String]): Unit = {
for(i <- 1 until 5 + 1){
println("宋宋,告别海狗人参丸吧" + i)
}
}
}
4.4.3 循环守卫
java中的continue
1)基本语法
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 + " ")
}
}
2)案例实操
需求:输出 1 到 5 中,不等于 3 的值
object TestFor {
def main(args: Array[String]): Unit = {
for (i <- 1 to 5 if i != 3) {
println(i + "宋宋")
}
}
}
4.4.4 循环步长
1)基本语法
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
说明:by 表示步长
2)案例实操
需求:输出 1 到 10 以内的所有奇数
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
输出结果
i=1
i=3
i=5
i=7
i=9
4.4.5 嵌套循环
1)基本语法
for(i <- 1 to 3; j <- 1 to 3) {
println(" i =" + i + " j = " + j)
}
说明:没有关键字,所以范围后一定要加;
来隔断逻辑
2)基本语法
上面的代码等价
for (i <- 1 to 3) {
for (j <- 1 to 3) {
println("i =" + i + " j=" + j)
}
}
4.4.6 引入变量
1)基本语法
for(i <- 1 to 3; j = 4 - i) {
println("i=" + i + " j=" + j)
}
说明:
(1)for 推导式一行中有多个表达式时,所以要加 ; 来隔断逻辑
(2)for 推导式有一个不成文的约定:当 for 推导式仅包含单一表达式时使用圆括号,当包含多个表达式时,一般每行一个表达式,并用花括号代替圆括号,如下
for {
i <- 1 to 3
j = 4 - i
} {
println("i=" + i + " j=" + j)
}
2)案例实操
上面的代码等价于
for (i <- 1 to 3) {
var j = 4 - i
println("i=" + i + " j=" + j)
}
4.4.7 循环返回值
1)基本语法
val res = for(i <- 1 to 10) yield i
println(res)
说明:将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字。
注意:开发中很少使用。
2)案例实操
需求:将原数据中所有值乘以 2,并把数据返回到一个新的集合中。
object TestFor {
def main(args: Array[String]): Unit = {
var res = for(i <-1 to 10) yield {
i * 2
}
println(res)
}
}
输出结果:
Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
4.4.8 倒序打印
1)说明:如果想倒序打印一组数据,可以用 reverse。
2)案例实操:
需求:倒序打印 10 到 1
for(i <- 1 to 10 reverse){
println(i)
}
4.5 While 和 do…While 循环控制
While 和 do…While 的使用和 Java 语言中用法相同。
4.5.1 While 循环控制
1)基本语法
循环变量初始化
while (循环条件) {
循环体(语句)
循环变量迭代
}
说明:
(1)循环条件是返回一个布尔值的表达式
(2)while 循环是先判断再执行语句
(3)与 for 语句不同,while 语句没有返回值,即整个 while 语句的结果是 Unit 类型()
(4)因为 while 中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量,而变量需要声明在 while 循环的外部,那么就等同于循环的内部对外部的变量造成了影响,所以不推荐使用,而是推荐使用 for 循环。
2)案例实操
需求:输出 10 句 “宋宋,喜欢海狗人参丸”
object TestWhile {
def main(args: Array[String]): Unit = {
var i = 0
while (i < 10) {
println("宋宋,喜欢海狗人参丸" + i)
i += 1
}
}
}
4.5.2 do…while 循环控制
1)基本语法
循环变量初始化;
do{
循环体(语句)
循环变量迭代
} while(循环条件)
说明
(1)循环条件是返回一个布尔值的表达式
(2)do…while 循环是先执行,再判断
2)案例实操
需求:输出 10 句 “宋宋,喜欢海狗人参丸”
object TestWhile {
def main(args: Array[String]): Unit = {
var i = 0
do {
println("宋宋,喜欢海狗人参丸" + i)
i += 1
} while (i < 10)
}
}
4.6 循环中断
1)基本说明
Scala 内置控制结构特地去掉了 break 和 continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。Scala中使用breakable控制结构来实现 break 和 continue 功能。
2)案例实操
需求 1:采用异常的方式退出循环
def main(args: Array[String]): Unit = {
try {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) throw new RuntimeException
}
}catch {
case e =>
}
println("正常结束循环")
}
需求 2:采用 Scala 自带的函数,退出循环
import scala.util.control.Breaks
def main(args: Array[String]): Unit = {
Breaks.breakable(
for (elem <- 1 to 10) {
println(elem)
if (elem == 5)
Breaks.break()
}
)
println("正常结束循环")
}
需求 3:对 break 进行省略
import scala.util.control.Breaks._
object TestBreak {
def main(args: Array[String]): Unit = {
breakable {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) break
}
}
println("正常结束循环")
}
}
需求 4:循环遍历 10 以内的所有数据,奇数打印,偶数跳过(continue)
object TestBreak {
def main(args: Array[String]): Unit = {
for (elem <- 1 to 10) {
if (elem % 2 == 1) {
println(elem)
} else {
println("continue")
}
}
}
}
4.7 多重循环
1)基本说明
(1)将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for,while,do…while 均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过 3 层】
(2)设外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要执行 m*n 次。
2)案例实操
需求:打印出九九乘法表
object TestWhile {
def main(args: Array[String]): Unit = {
for (i <- 1 to 9) {
for (j <- 1 to i) {
print(j + "*" + i + "=" + (i * j) + "\t")
}
println()
}
}
}
第 5 章 函数式编程
关心的映射关系
1)面向对象编程
解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题。
对象:用户
行为:登录、连接 JDBC、读取数据库
属性:用户名、密码
Scala 语言是一个完全面向对象编程语言。万物皆对象
对象的本质:对数据和行为的一个封装
2)函数式编程
解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
例如:请求->用户名、密码->连接 JDBC->读取数据库
Scala 语言是一个完全函数式编程语言。万物皆函数。
函数的本质:函数可以当做一个值进行传递
3)在 Scala 中函数式编程和面向对象编程完美融合在一起了。
5.1 函数基础
5.1.1 函数基本语法
1)基本语法
2)案例实操
需求:定义一个函数,实现将传入的名称打印出来。
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)函数定义
def f(arg: String): Unit = {
println(arg)
}
// (2)函数调用
// 函数名(参数)
f("hello world")
}
}
5.1.2 函数和方法的区别
1)核心概念
(1)为完成某一功能的程序语句的集合,称为函数。
(2)类中的函数称之方法。
2)案例实操
(1)Scala 语言可以在任何的语法结构中声明任何的语法
(2)函数没有重载和重写的概念;方法可以进行重载和重写
(3)Scala 中函数可以嵌套定义
object TestFunction {
// (2)方法可以进行重载和重写,程序可以执行
def main(): Unit = {
}
def main(args: Array[String]): Unit = {
// (1)Scala 语言可以在任何的语法结构中声明任何的语法
import java.util.Date
new Date()
// (2)函数没有重载和重写的概念,程序报错
def test(): Unit ={
println("无参,无返回值")
}
test()
def test(name:String):Unit={
println()
}
//(3)Scala 中函数可以嵌套定义
def test2(): Unit ={
def test3(name:String):Unit={
println("函数可以嵌套定义")
}
}
}
}
package chapter05
object Test01_FunsctionAndMethod {
def main(args: Array[String]): Unit = {
// 定义函数
def sayHi(name:String):Unit = {
println("hi,"+name)
}
// 调用函数
sayHi("alice")
// 调用对象方法
Test01_FunsctionAndMethod.sayHi("Bob")
// 获取方法返回值
val result = Test01_FunsctionAndMethod.sayHello("cary")
println(result)
}
// 定义对象的方法
def sayHi(name:String):Unit = {
println("Hi,"+name)
}
def sayHello(name:String):String = {
println("hello,"+name)
return "hello"
}
}
5.1.3 函数定义
1)函数定义
(1)函数 1:无参,无返回值
(2)函数 2:无参,有返回值
(3)函数 3:有参,无返回值
(4)函数 4:有参,有返回值
(5)函数 5:多参,无返回值
(6)函数 6:多参,有返回值
2)案例实操
package chapter05
object Test02_FuntionDefine {
def main(args: Array[String]): Unit = {
// 函数 1:无参,无返回值
def test1(): Unit ={
println("无参,无返回值")
}
test1()
// 函数 2:无参,有返回值
def test2():String={
return "无参,有返回值"
}
println(test2())
// 函数 3:有参,无返回值
def test3(s:String):Unit={
println(s)
}
test3("jinlian")
// 函数 4:有参,有返回值
def test4(s:String):String={
return s+"有参,有返回值"
}
println(test4("hello "))
// 函数 5:多参,无返回值
def test5(name:String, age:Int):Unit={
println(s"$name, $age")
}
test5("dalang",40)
}
}
5.1.4 函数参数
1)案例实操
(1)可变参数
(2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
(3)参数默认值,一般将有默认值的参数放置在参数列表的后面
(4)带名参数
package chapter05
object Test03_FunctionParameter {
def main(args: Array[String]): Unit = {
// (1)可变参数
def test( s : String* ): Unit = {
println(s)
}
// 有输入参数:输出 Array
test("Hello", "Scala") // WrappedArray(Hello, Scala)
// 无输入参数:输出 List()
test()
// (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
def test2( name : String, s: String* ): Unit = {
println(name + "," + s)
}
test2("jinlian", "dalang")
// (3)参数默认值
def test3( name : String, age : Int = 30 ): Unit = {
println(s"$name, $age")
}
// 如果参数传递了值,那么会覆盖默认值
test3("jinlian", 20)
// 如果参数有默认值,在调用的时候,可以省略这个参数
test3("dalang")
// 一般情况下,将有默认值的参数放置在参数列表的后面
def test4( sex : String = "男", name : String ): Unit = {
println(s"$name, $sex")
}
// Scala 函数中参数传递是,从左到右
//test4("wusong")
//(4)带名参数
test4(name="ximenqing")
}
}
5.1.5 函数至简原则(重点)
函数至简原则:能省则省
1)至简原则细节
(1)return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
(2)如果函数体只有一行代码,可以省略花括号
(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
(4)如果有 return,则不能省略返回值类型,必须指定
(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
(6)Scala 如果期望是无返回值类型,可以省略等号
(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
2)案例实操
package chapter05
object Test04_Simplify {
def main(args: Array[String]): Unit = {
// (0)函数标准写法
def f( s : String ): String = {
return s + " jinlian"
}
println(f("Hello"))
// 至简原则:能省则省
//(1) return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
def f1( s : String ): String = {
s + " jinlian"
}
println(f1("Hello"))
//(2)如果函数体只有一行代码,可以省略花括号
def f2(s:String):String = s + " jinlian"
//(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起 省略)
def f3( s : String ) = s + " jinlian"
println(f3("Hello3"))
//(4)如果有 return,则不能省略返回值类型,必须指定。
def f4() :String = {
return "ximenqing4"
}
println(f4())
//(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也 不起作用
def f5(): Unit = {
return "dalang5"
}
println(f5())
//(6)Scala 如果期望是无返回值类型,可以省略等号
// 将无返回值的函数称之为过程
def f6() {
"dalang6"
}
println(f6())
//(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可 不加
def f7() = "dalang7"
println(f7())
println(f7)
//(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省 略
def f8 = "dalang"
//println(f8())
println(f8)
//(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def f9 = (x:String)=>{println("wusong")}
// 匿名函数 lambda表达式
(x:String)=>{println("wusong")}
def f10(f:String=>Unit) = {
f("")
}
f10(f9)
println(f10((x:String)=>{println("wusong")}))
}
}
5.2 函数高级
5.2.1 高阶函数
在 Scala 中,函数是一等公民。怎么体现的呢?
对于一个函数我们可以:定义函数、调用函数
package chapter05
object Test06_HighOrdreFunction {
def main(args: Array[String]): Unit = {
// 有参数有返回值
def f(n: Int): Int = {
println("f调用")
n + 1
}
// 没有参数
def fun(): Int = {
println("f调用")
1
}
val result: Int = f(123)
println(result)
fun()
fun
// 1. 函数可以作为值 进行传递, 前面试过将 匿名函数定义一个名称
val f1 = f _ // 名称+空格+下划线 表示 的是这个函数而不是函数的调用
val f2: Int => Int = f // 也可以明确指定返回值类型
println(f1) // 打印的是函数的引用地址 都是一个函数对象
println(f1(12))
println(f2)
println(f2(35))
val f3 = fun
val f4 = fun _
val f5: () => Int = fun
println(f3)
println(f4)
println(f5)
// 2. 函数作为参数进行传递
// 定义一个二元计算函数
def dualEval(op: (Int, Int) => Int, a: Int, b: Int): Int = {
op(a, b)
}
def add(a: Int, b: Int): Int = {
a + b
}
println(dualEval(add, 12, 35))
println(dualEval((a, b) => a + b, 12, 35))
println(dualEval(_ + _, 12, 35))
// 3. 函数作为函数的返回值返回
def f7(): Int => Unit = {
def f6(a: Int): Unit = {
println("f6调用" + a)
}
f6 // 当函数直接返回
}
// val f6= f7()
// println(f6) // 函数调用
// println(f6(25)) // f6调用的方法 返回的是Unit类型,所以是()
println(f7()(25))
}
}
object TestFunction {
def main(args: Array[String]): Unit = {
// 调用函数
foo()
}
// 定义函数
def foo():Unit = {
println("foo...")
}
}
但是其实函数还有更高阶的用法
1)函数可以作为值进行传递
object TestFunction {
def main(args: Array[String]): Unit = {
//(1)调用 foo 函数,把返回值给变量 f
//val f = foo()
val f = foo
println(f)
//(2)在被调用函数 foo 后面加上 _,相当于把函数 foo 当成一个整体, 传递给变量 f1
val f1 = foo _
foo()
f1()
//(3)如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给 变量
var f2:()=>Int = foo
}
def foo():Int = {
println("foo...")
1
}
}
2)函数可以作为参数进行传递
def main(args: Array[String]): Unit = {
// (1)定义一个函数,函数参数还是一个函数签名;f 表示函数名称;(Int,Int) 表示输入两个 Int 参数;Int 表示函数返回值
def f1(f: (Int, Int) => Int): Int = {
f(2, 4)
}
// (2)定义一个函数,参数和返回值类型和 f1 的输入参数一致
def add(a: Int, b: Int): Int = a + b
// (3)将 add 函数作为参数传递给 f1 函数,如果能够推断出来不是调用,_ 可以省略
println(f1(add))
println(f1(add _))
//可以传递匿名函数
}
3)函数可以作为函数返回值返回
def main(args: Array[String]): Unit = {
def f1() = {
def f2() = {
}
f2 _
}
val f = f1()
// 因为 f1 函数的返回值依然为函数,所以可以变量 f 可以作为函数继续调用
f()
// 上面的代码可以简化为
f1()()
}
5.2.2 匿名函数
1)说明
没有名字的函数就是匿名函数。
(x:Int)=>{函数体}
x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑
2)案例实操
需求 1:传递的函数有一个参数
传递匿名函数至简原则:
(1)参数的类型可以省略,会根据形参进行自动的推导
(2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
(3)匿名函数如果只有一行,则大括号也可以省略
(4)如果参数只出现一次,则参数省略且后面参数可以用_代替
package chapter05
object Test05_Lambda {
def main(args: Array[String]): Unit = {
// 这个匿名函数的类型为 String=>Unit , 可以通过。var 查看
val fun = (x: String) => {
println(x)
}
fun("wanghaha")
// 定义一个函数,以函数作为参数输入
// 把要做的操作作为函数 传进来, 而要操作的数据是写死的。
def f(func: String => Unit): Unit = {
func("wangha")
}
f(fun)
f((x: String) => {
println(x)
})
// 匿名函数的简化原则
// (1)参数的类型可以省略,会根据形参进行自动的推导
f((x) => {
println(x)
})
// (2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
f(x => {
println(x)
})
// (3)匿名函数如果只有一行,则大括号也可以省略
f(x => println(x))
// (4)如果参数只出现一次,则参数省略且后面参数
f(println(_))
// (5) 如果可以推断出 当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线
f(println)
}
}
def main(args: Array[String]): Unit = {
// (1)定义一个函数:参数包含数据和逻辑函数
def operation(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
// (2)定义逻辑函数
def op(ele: Int): Int = {
ele + 1
}
// (3)标准函数调用
val arr = operation(Array(1, 2, 3, 4), op)
println(arr.mkString(","))
// (4)采用匿名函数
val arr1 = operation(Array(1, 2, 3, 4), (ele: Int) => {
ele + 1
})
println(arr1.mkString(","))
// (4.1)参数的类型可以省略,会根据形参进行自动的推导;
val arr2 = operation(Array(1, 2, 3, 4), (ele) => {
ele + 1
})
println(arr2.mkString(","))
// (4.2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情 况:没有参数和参数超过 1 的永远不能省略圆括号。
val arr3 = operation(Array(1, 2, 3, 4), ele => {
ele + 1
})
println(arr3.mkString(","))
// (4.3) 匿名函数如果只有一行,则大括号也可以省略
val arr4 = operation(Array(1, 2, 3, 4), ele => ele + 1)
println(arr4.mkString(","))
//(4.4)如果参数只出现一次,则参数省略且后面参数可以用_代替
val arr5 = operation(Array(1, 2, 3, 4), _ + 1)
println(arr5.mkString(","))
}
需求 2:传递的函数有两个参数
object TestFunction {
def main(args: Array[String]): Unit = {
def calculator(a: Int, b: Int, op: (Int, Int) => Int): Int
= {
op(a, b)
}
// (1)标准版
println(calculator(2, 3, (x: Int, y: Int) => {x + y}))
// (2)如果只有一行,则大括号也可以省略
println(calculator(2, 3, (x: Int, y: Int) => x + y))
// (3)参数的类型可以省略,会根据形参进行自动的推导;
println(calculator(2, 3, (x , y) => x + y))
// (4)如果参数只出现一次,则参数省略且后面参数可以用_代替
println(calculator(2, 3, _ + _))
}
}
// 实际示例 ,定义一个”二元运算“函数,只操作1和2两个数,但是具体运算通过参数传入
def dualFunctionOneAndTwo(fun:(Int,Int)=>Int): Int ={
fun(1,2)
}
val add = (a:Int , b:Int)=> a+b
val minus = (a:Int , b:Int)=> a-b
println(dualFunctionOneAndTwo(add))
println(dualFunctionOneAndTwo(minus))
// 匿名函数简化
println(dualFunctionOneAndTwo((a:Int , b:Int)=> a+b))
println(dualFunctionOneAndTwo((a:Int , b:Int)=> a-b))
println(dualFunctionOneAndTwo((a , b)=> a+b))
println(dualFunctionOneAndTwo( _+_))
println(dualFunctionOneAndTwo( _-_))
println(dualFunctionOneAndTwo((a , b)=> b-a))
println(dualFunctionOneAndTwo((a , b)=> -a+b))
println(dualFunctionOneAndTwo( -_-_))
package chapter05
object Test07_Practice_CollectionOperation {
def main(args: Array[String]): Unit = {
val arr: Array[Int] = Array(14,24,45,98)
// 对数组进行处理, 将操作抽象出来, 处理完毕之后的结果返回一个新的数组
def arrayOperation(array: Array[Int], op : Int=>Int ): Array[Int]={
for(elem <- array) yield op(elem)
}
// 定义一个加一操作
def addOne(elem : Int) = {
elem+1
}
// 调用函数
val newArray : Array[Int] = arrayOperation(arr, addOne _)
println(newArray.mkString(","))
// 传入匿名函数 , 实现元素翻倍
val newArray2 = arrayOperation(arr, _ * 2)
println(newArray2.mkString(","))
}
}
扩展练习
练习 1:定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。
要求调用函数 fun(0, “”, ‘0’)得到返回值为 false,其它情况均返回 true。
def main(args: Array[String]): Unit = {
var fun = (a:Int,b:String,c:Char) => { if (a==0 && b=="" && c=='0') false else true}
println(fun(0,"",'0'))
println(fun(0,"a",'0'))
}
练习 2: 定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接收一个 Char 类型的参数,返回一个 Boolean 的值。
要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true。
def func(a :Int): String=> (Char =>Boolean) = {
def func1(b : String) : Char =>Boolean = {
def func2(c:Char ): Boolean ={
if (a==0 && b=="" && c=='0') false else true
}
func2
}
func1
}
println(func(0)("")('0'))
println(func(0)("")('1'))
// 匿名函数的简写
def func3(a :Int): String=> (Char =>Boolean) = {
b => c =>if (a==0 && b=="" && c=='0') false else true
}
// 柯里化
def func4(a:Int)(b:String)(c:Char):Boolean = {
if (a==0 && b=="" && c=='0') false else true
}
5.2.3 高阶函数案例
需求:模拟 Map 映射、Filter 过滤、Reduce 聚合
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)map 映射
def map(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
val arr = map(Array(1, 2, 3, 4), (x: Int) => {
x * x
})
println(arr.mkString(","))
// (2)filter 过滤。有参数,且参数再后面只使用一次,则参数省略且
后面参数用_表示
def filter(arr:Array[Int],op:Int =>Boolean) ={
var arr1:ArrayBuffer[Int] = ArrayBuffer[Int]()
for(elem <- arr if op(elem)){
arr1.append(elem)
}
arr1.toArray
}
var arr1 = filter(Array(1, 2, 3, 4), _ % 2 == 1)
println(arr1.mkString(","))
// (3)reduce 聚合。有多个参数,且每个参数再后面只使用一次,则参
数省略且后面参数用_表示,第 n 个_代表第 n 个参数
def reduce(arr: Array[Int], op: (Int, Int) => Int) = {
var init: Int = arr(0)
for (elem <- 1 until arr.length) {
init = op(init, elem)
}
init
}
//val arr2 = reduce(Array(1, 2, 3, 4), (x, y) => x * y)
val arr2 = reduce(Array(1, 2, 3, 4), _ * _)
println(arr2)
}
}
5.2.4 函数柯里化&闭包
闭包:函数式编程的标配
1)说明
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包。解决内层函数访问外部变量的问题,因为内层函数返回时,外部变量的值已经释放掉了,但是可以通过对释放掉的外部变量进行打包。
调用函数(jvm虚拟机栈里的栈帧,func1调用,func1弹出,func2调用,func2弹出)时,相当于调用对象实例(保存在jvn虚拟机堆里),然后在对象实例里,把相关的,它所依赖的外部环境和局部变量打包保存在对象实例
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
2)案例实操
(1)闭包
package chapter05
object Test09_ClosureAndCurrying {
def main(args: Array[String]): Unit = {
def add(a:Int, b:Int) :Int = {
a + b
}
// 1. 考虑固定一个加数的场景
def addByFour(b:Int):Int = {
4 + b
}
// 2. 扩展固定加数改变的情况
def addByFive(b:Int):Int = {
5 + b
}
// 3. 将固定加数作为另一个参数传入,但是作为”第一层参数传入“
def addByFour1():Int=>Int = {
val a = 4
def addB(b:Int) :Int = {
a + b
}
addB
}
// 闭包
def addByA(a:Int) :Int => Int = {
def addB(b: Int): Int = {
a + b
}
addB
}
println(addByA(35)(23))
val addByFour2 = addByA(4)
println(addByFour2( 45))
// 4. lambda表达式的简写
def addByA3(a:Int) :Int=>Int = a + _
// 5. 柯里化
def addCurrying(a:Int)(b:Int):Int = a + b
}
}
object TestFunction {
def main(args: Array[String]): Unit = {
def f1()={
var a:Int = 10
def f2(b:Int)={
a + b
}
f2 _
}
// 在调用时,f1 函数执行完毕后,局部变量 a 应该随着栈空间释放掉
val f = f1()
// 但是在此处,变量 a 其实并没有释放,而是包含在了 f2 函数的内部,形成了闭合的效果
println(f(3))
println(f1()(3))
// 函数柯里化,其实就是将复杂的参数逻辑变得简单化,函数柯里化一定存在闭包
def f3()(b:Int)={
a + b
}
println(f3()(3))
}
}
5.2.5 递归/尾递归
阶乘
递归算法
- 方法调用自身
- 方法必须要有跳出的逻辑
- 方法调用自身时,传递的参数应该有规律
- scala 中的递归必须声明函数返回值类型
1)说明
一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
2)案例实操
package chapter05
import scala.annotation.tailrec
object Test10_recursion {
def main(args: Array[String]): Unit = {
println(fact(5))
println(tailFact(5))
}
// 递归实现计算阶乘
def fact(i: Int):Int = {
if (i == 0) return 1
fact(i-1)*i
}
// 尾递归实现
def tailFact(n:Int):Int = {
@tailrec
def loop(n:Int, currentRes:Int):Int = {
if (n == 0) return currentRes
loop(n - 1, currentRes * n)
}
loop(n,1)
}
}
5.2.6 控制抽象
1)值调用:把计算后的值传递过去
object TestControl {
def main(args: Array[String]): Unit = {
def f = ()=>{
println("f...")
}
foo(f())
}
def foo(a: Int):Unit = {
println(a)
println(a)
}
}
2)名调用:把代码传递过去
object TestControl {
def main(args: Array[String]): Unit = {
def f = ()=>{
println("f...")
10
}
foo(f())
}
//def foo(a: Int):Unit = {
def foo(a: =>Int):Unit = {//注意这里变量 a 没有小括号了
println(a)
println(a)
}
}
输出结果:
```scala
f...
10
f...
10
注意:Java 只有值调用;Scala 既有值调用,又有名调用。
3)案例实操
package chapter05
object Test11_ControlAbstration {
def main(args: Array[String]): Unit = {
// 1. 传递参数
def f0(a:Int): Unit = {
println("a : " + a)
println("a : " + a)
}
f0(23)
def f1():Int = {
println("f1 调用")
12
}
f0(f1())
println("00000000")
// 2. 传名参数 传递的是 代码块
// =>Int 类型 表示一块代码块 返回值为int
def f2(a: =>Int): Unit = {
println("a : " + a)
println("a : " + a)
}
f2(f1())
f2(23)
}
}
自定义一个 While 循环
package chapter05
object Test12_MyWhile {
def main(args: Array[String]): Unit = {
var n = 10
// 1. 常规的while循环
while (n >= 1){
println(n)
n -= 1
}
// 2. 用闭包实现一个函数 , 将代码块作为参数传入
// 第一个时条件, 第二个是循环体的代码块, 都是名传递
// 递归调用
def myWhile(codition: =>Boolean) :( => Unit)=> Unit = {
// 内层函数需要递归调用 ,参数就是循环体
def doLoop(op: =>Unit):Unit = {
if(codition){
op
myWhile(codition)(op)
}
}
doLoop _
}
n = 10
myWhile( n>=1){
println(n)
n -= 1
}
// 3. 用匿名函数实现
def myWhile2(codition: =>Boolean) :( => Unit)=> Unit = {
// 内层函数需要递归调用 ,参数就是循环体
op => {
if(codition){
op
myWhile2(codition)(op)
}
}
}
n = 10
myWhile2( n>=1){
println(n)
n -= 1
}
// 4. 用柯里化实现
def myWhile3(codition : =>Boolean)(op: => Unit):Unit = {
if(codition) {
op
myWhile3(codition)(op)
}
}
n = 10
myWhile3( n>=1){
println(n)
n -= 1
}
}
}
5.2.7 惰性加载
1)说明
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。
2)案例实操
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 30)
println("----------------")
println("res=" + res)
}
def sum(n1: Int, n2: Int): Int = {
println("sum 被执行。。。")
return n1 + n2
}
输出结果:
----------------
sum 被执行。。。
res=40
注意:lazy 不能修饰 var 类型的变量
更多推荐
【学习笔记】大数据技术之Scala(上)
发布评论