二, Scala 函数式编程详解
五, 函数式编程
- 面向对象编程(OOP)
- 面向对象思想: 使用对象来映射现实中的事物, 使用对象的关系来描述事事物之间的联系
- 面向对象编程:
把要解决的问题按照一定的规则划分为多个独立的对象, 这些对象拥有各自的属性和方法, 通过调用对象的方法来解决问题;
[举个栗子]
用户通过JDBC连接和读取数据库
- 对象: 用户;
- 属性: 用户名, 密码;
- 行为(方法): 登录, 连接JDBC, 读取数据库;
对象的本质: 对数据和行为的一个封装
- 函数式编程(FP)
- 面向过程编程:
分析解决问题所需要的步骤, 然后用函数把这些步骤一一实现;
[举个栗子]
用户通过JDBC连接和读取数据库
- 请求->用户名,密码->连接JDBC->读取数据库
函数的本质: 函数可以当做一个值进行传递
5.1 函数基础
- Scala 既是一个完全面向对象编程语言, 万物皆对象, 不像Java那样是半吊子OO(基本数据类型经过包装才能作为对象);
- Scala 又是一个完全函数式的编程语言, 万物皆为函数;
5.1.1 函数基本语法

5.1.2 函数和方法的区别
- 概念:
- 函数: 未完成某一个功能的程序语句的集合
- 方法: 类中的函数称之为方法
- Scala中的函数:
- Scala语言可以在任何的语法结构中声明任何的语法;
- 函数没有重载和重写的概念, 而方法可以进行重载, 重写;
- Scala 中函数可以嵌套定义;

5.1.3 函数的参数
- 可变参数; def function(args : String*)
- 参数默认值; def function(args : String = “default args”)
- 带名参数; function(agrs = “参数值”) 调用方法时使用
Object functionTest{
//可变参数, 会在参数类型后加一个*标记, 如果函数有多个参数时, 要把可变参数放在参数列表的后面
def funcOne(name : String, age : Int, subJect : String): Unit = {
println("Hello, I am ${name}, ${age} years old, i have some examnation test today, like ${subJect}")
}
def funcTwo(name: String, age : Int = 18): Unit = {
println(s"$name, $age");
}
//如果参数传递了值, 会把默认值替换掉, 如果不要替换, 可以直接省略这个字段的传值
funcTwo("李四")
}
5.1.4 函数至简原则
| 情形 | 简化 |
|---|---|
| 1. 标准写法 | 按部就班, 但是注意 return是可以省略的 |
| 2. 函数只有一行代码 | {}可省略 |
| 3. 返回值类型可推断 | :返回值类型可省略, 方法中return语句时, 不可省略! |
| 4. 函数返回空(Unit) | return 关键字失效 |
| 5. 返回值类型可推断 + 函数返回为空 | : 返回值而类型 =直接省略 |
| 6. 函数没有参数, 但是有参数列表小括号 | 调用此函数, 括号可有可无 |
| 6.1函数没有参数, 参数列表小括号已经省略 | 调用此函数时小括号也必须要省略! |
| (重要!)7. 如果不关心函数名称, 只关心逻辑处理 | def 函数名, = 返回值类型全部省略. (参数列表) => {函数体} |
(参数列表) => {函数体}, 又叫匿名函数, 或者是 lamda表达式, 对, 就是等同于Java8的那个, 只不过写法稍微有点差别: Java8中的lamda表达式为 (参数列表) -> {函数体}
[举个栗子: ]
object TestSimplify {
def main(args: Array[String]): Unit = {
// 1. 函数标准写法
// 在Scala中, return是冗余的, Scala会把带返回值的函数的最后一行直接返回
def f1(name: String): String ={
//return name + "你好"
name + "你好"
}
println(f1("lisi"))
//2. 函数体只有一行代码, 可省略花括号
def f2(name: String): String = return name + "你好"
println(f2("zhangsan"))
//3.返回值类型如果可推断, 可以省略 ':返回值类型',
// 但是, 注意噢, 如果方法中有return语句时是不可省略返回值类型的
def f3(name: String)= println(name + ", 你好")
f3("wangwu")
//4. 函数声明返回为空(Unit)时, return 关键字失效
//5.Scala 如果期望是无返回值类型,可以省略等号
def f5(name: String){
println(s"hello, ${name}")
}
f5("liuqi")
//6. 函数没有参数, 但是有参数列表小括号, 调用此函数时, 括号可加可不加
//6. 函数没有参数, 参数列表小括号已经省略, 调用此函数时小括号也必须要省略!
def f6(): Unit = {
println("hello, f6")
}
//
f6
f6()
def f6_1: Unit = {
println("hello, f6_1")
}
f6_1
//7. 如果不关心函数名称, 只关心逻辑处理, 那么 'def 函数名' 可以省略掉
//也叫做是匿名函数/lamda表达式
//7.1 普通方法的书写
def f7_1(name: String): Unit = {
println("hello, " + name)
}
//省略 `def f7` 和 `: 返回值类型 = `
// 格式: (参数列表) => {方法体}
(name: String) => {
println("hello, " + name)
}
//那么. 如何调用这个匿名函数呢?==> 让他有名字
var f7_2 = (name: String) =>{
println("hello, motherfucker" + name)
}
f7_2("sww")
}
}
5.2 函数高级
5.2.1 匿名函数
5.2.1.1 概念和使用
- 简单再说下what is 匿名函数?
- 如果对一个函数我们不关心函数名称和返回值, 只去聚焦于函数的实现逻辑, 那么我们可以使用匿名函数
- 写法: (参数列表) => {函数体}
- 在对匿名函数化简举栗子之前, 我们先预习下函数的高级用法之一: 把一个函数作为另一个函数的参数, 即函数作为参数进行传递:
//匿名函数
// 匿名函数的函数名称. String => Unit (参数类型) => 返回值类型
val hiFunc: (String, Int) => Unit = (name: String, age: Int) => {
println("hello, my name is " + name)
}
//把匿名函数作为参数
// 函数作为另一个函数的参数,写法: 函数名: (参数列表) => 返回值类型
def Self_Introduction(hiFunction : (String, Int) => Unit): Unit = {
hiFunction("李四", 25) // 形参噢
}
Self_Introduction(hiFunc) //实参
从上面的栗子可以得出两点:
- 匿名函数的函数名写法: var 匿名函数名称 : (参数列表) => 返回值类型 , 如: var f1: (name: String, age: Int) => Unit, 有这么个匿名函数, 返回值为空, 参数列表包括String类型的name和Int类型的age , 我们让它的函数名为f,
- 把匿名函数作为另一个函数的参数: def f2(匿名或普通函数的函数名 : (参数类型 => 返回值类型)) , 如, def f2(f1: (name: String, age: Int) => Unit, addr: String)
5.2.1.2 匿名函数简化规则
匿名函数至简原则:
- 参数类型可以省略, 会根据形参进行自动推导;
- 类型省略后, 只有一个参数的, 可以省略小括号; 没有参数和参数超过1的. 不能省略圆括号;
- 匿名函数只有一行, 则大括号也可省略;
- 参数只出现一次, 则参数省略且后面的参数可用
_代替;
在举栗子之前, 先明确一个知识点:
如何定义一个变量, 这个变量的类型是一个参数类型为(name: String, age: Int), 返回值为空的函数?
答案就是: var f: (String, Int) => Unit = 匿名函数/普通函数
[举个栗子]
object TestAnonymousSimplify {
def main(args: Array[String]): Unit = {
//1. 匿名函数标准写法
//标准匿名函数: (参数列表) => {方法体}
// 这个匿名函数的函数名我们设置为helloFunc
var helloFunc: String => Unit = (name: String) => {
println("hello, " + name)
}
//1. 未化简的普通函数f0(匿名函数helloFunc作为参数传递进去)
//标准的把函数作为另一个函数的参数写法: 函数名: 参数类型 => 返回值类型
def f0(helloFunction: String => Unit, name: String): Unit={
helloFunc(name)
}
//1.1 未化简的匿名函数f1(匿名函数helloFunc作为参数传递进去)
// 注意这里 f1 : (String => Unit, String) => Unit, :后面的都是为了描述f1这个函数的,
// 这句话表明, f1这个函数的参数列表是两个参数,
// 第一个参数是一个函数, 这个函数有一个参数, 类型为String, 返回值为空;
// 第二个参数的参数类型为String
// 所以定义一个函数变量, 就像是定义常见变量一样, 比如 name: String, 就是表示了一个String类型的变量name
var f1: (String=> Unit, String) => Unit = (helloFunction: String => Unit, name: String) =>{
helloFunction(name)
}
f1(helloFunc, "我是f1")
化简的几种方法
//2. 参数类型可省略. 因为会根据实参(传入的参数)进行自动推导
// f2
var f2: (String => Unit, String) => Unit = (helloFunction, name) => {
helloFunction(name)
}
f2(helloFunc, "我是f2")
//3. 参数类型省略后, 一个参数的可以省略小括号, 没有参数或参数超过1个, 不能省略圆括号
//本例子, 只对f3传入一个函数作为参数, name就不传入了
var f3: (String => Unit) => Unit = helloFunc => {
helloFunc("我是f3")
}
f3(helloFunc)
//4. 参数只出现一次, 则参数省略且后面的参数可用`_`代替
var f4: (String => Unit) => Unit = (_) =>{
helloFunc("我是f4")
}
f4(helloFunc)
}
}
这里我们总结一下前面用到的各种花里胡哨的写法, 包括匿名函数的定义, 函数类型的参数, 把函数作为参数传给另一个函数:
| 写法 | 解释 |
|---|---|
| (name:String, age: Int) => {函数体} | 匿名函数写法就是()=>{}, (参数列表)=>{函数体} |
| var f = (String, Int) => Unit = {函数体} | 函数类型的参数写法就是 (参数列表中的数据类型) => 返回值 |
| var f1 = ((f的参数类型)=> f1的返回值, String, name) => String | 函数作为函数的参数写法就是对函数类型参数写法的套娃 |
5.2.2 函数的高阶用法
对于一个函数, 我们可以: 定义函数, 调用函数
Obeject TestFuction{
def main(args: Array[String]): Unit = {
//调用函数
foo()
}
//定义函数
def foo(): Unit = {
println("foo...")
}
}
但是, 函数还有更高阶的用法: 包括作为值进行传递, 作为参数进行传递, 作为函数返回值返回;
5.2.2.1 函数作为值进行传递
- 在Scala中, 直接对函数进行输出时, 都会有相应的值, 当函数有返回值时, 输出的是返回值, 当函数返回值为Unit(空)时, 输出的是一对括号 ();
- 所以我们可以把函数作为值进行传递, 直接传递给某一个变量, 输出的是函数的返回值;
- 我们也可以直接把函数整体的赋值给某个变量, 从而使这个变量跟原始函数一样使用;
object TestValTrans {
def main(args: Array[String]): Unit = {
def fool(name: String, age: Int): Int = {
println(s"my name is ${name}, i am ${age} years old.")
8
}
//(1)调用 fool 函数,把返回值给变量 f
// fool没有参数时, 可写为: var fun = fool
var fun = fool("zhangssan", 13)
//(2)在被调用函数 foo 后面加上 _,相当于把函数 foo 当成一个整体, 传递给变量 f1
var fun_all = fool _
println(fool("wangwu", 28))
println(fun_all("wangwu", 28))
//(3)如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给 变量
var fun_all_1:(String, Int) => Int = fool
print(fun_all_1("刘2", 46))
}
}
5.2.2.2 函数作为参数进行传递
- 函数作为参数: (函数参数1的数据类型, 参数2的数据类型…) => 函数返回值类型
object TestFunc {
def main(args: Array[String]): Unit = {
//1.函数普通写法
def hello(name: String, age: Int): Unit = {
println(s"my name is ${name}, i am ${age} years old.")
}
//2. 匿名函数写法
// (name: String, age: Int) => {
// println(s"my name is ${name}, i am ${age} years old.")
// }
//3. 把前面的函数1作为参数传递给下面的函数
def func( helloFunction: (String, Int) => Unit, name1: String, age1: Int): Unit ={
helloFunction(name1, age1) //helloFuntion是形参噢
}
func(hello, "lisi", 28) //调用函数, 传入实参, hello函数就是实参
}
}
5.2.2.3 函数作为函数返回值返回
def main(args: Array[String]): Unit = {
def f1() = {
def f2() = {
}
f2 _
}
val f = f1()
// 因为 f1 函数的返回值依然为函数,所以可以变量 f 可以作为函数继续调用
f()
// 上面的代码可以简化为
f1()()
来两个小练习:
object AnonymousPractice {
def main(args: Array[String]): Unit = {
//1.
var f0: (Int, String, Char) => Boolean = (id: Int, name: String, ch: Char) =>{
if(id.equals(0) && name.equals("") && ch.equals('0'))
false
else
true
}
f1
}
//2.1 对上面第二道题答案的简化( 匿名函数)
def func(x: Int): String => (Char => Boolean) = {
(y: String) => {
(z: Char) => {
if(x == 0 && y == "" && z == '0')
false
else
true
}
}
}
//2.2 继续简化
}
5.2.2.4 高阶函数使用案例
待补充
5.2.3 函数柯里化&闭包
闭包: 如果一个函数, 访问到了它的外部(局部)变量的值, 这个函数和它所处的环境, 称为闭包
详细原理: 待补充
函数柯里化: 把一个参数列表中的多个参数, 变成多个参数列表

5.2.4 递归调用
- 一个函数/方法在函数/方法体内又调用了本身, 我们称之为递归调用;

5.2.5 控制抽象(函数参数的传递)
-
值调用: 把计算后的值传递过去
-
名调用: 把代码传递过去
注意: Java只有值调用, Scala既有值调用, 又有名调用;
[综合案例_ 实现While循环功能]
5.2.6 惰性加载
- 当
函数返回值被声明为 lazy时, 函数的执行将被推迟, 直到我们首次对此取值, 该函数才会执行, 这种函数我们称之为惰性函数; - 注意噢, 首次请求该函数的返回值时, 这个函数才会被执行!
注意: lazy不能修饰var类型的变量


浙公网安备 33010602011771号