Scala(3) 函数式编程
2023-08-09 14:53:19 # Big Data # Scala

5. 函数式编程

  1. 面向对象编程:

    解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题

    Scala是一个完全面向对象编程语言。万物皆对象

    对象的本质:对数据和行为的一个封装

  2. 函数式编程

    解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题

    Scala是一个完全函数式编程语言。万物皆函数

    函数的本质:函数可以当作一个值进行传递

  3. Scala中函数式编程和面向对象编程完美融合在一起

5.1 函数基础

5.1.1 函数基本语法

基本语法

image-20220920151844943

案例实操

1
2
3
4
5
6
7
8
def main(args: Array[String]): Unit = {
// 函数定义
def f(arg: String): Unit = {
println(arg)
}
// 函数调用
f("Hello World")
}

5.1.2 函数和方法的区别

核心概念

  • 为完成某一功能的程序语句的集合,称为函数
  • 类中的函数称为方法

案例实操

  • Scala可以在任何的语法结构中声明任何的语法
  • 函数没有重载和重写的概念,方法可以进行重载和重写
  • Scala中函数可以嵌套定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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(name)
}
test3("函数可以嵌套定义")
}
test2()
}
}

5.1.3 函数定义

基础语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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("有参, 无返回值")
// 函数4: 有参, 有返回值
def test4(s: String): String = {
return s
}
println(test4("有参, 有返回值"))
// 函数5: 多参, 无返回值
def test5(name: String, age: Int): Unit = {
println(s"${name}, ${age}")
}
test5("tony", 18)
// 函数6: 多参, 有返回值
def test6(name: String, age: Int): String = {
return s"${name}, ${age}"
}
println(test6("tony", 18))
}

5.1.4 函数参数

案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def main(args: Array[String]): Unit = {
// 1.可变参数
def test(s: String*): Unit = {
println(s)
}
// 有输入参数: 输出 WrappedArray(Hello, Scala)
test("Hello", "Scala")
// 无输入参数: 输出 List()
test()

// 2.如果参数列表中存在多个参数, 那么可变参数一般放置在最后
def test2(name: String, s: String*): Unit = {
println(name + "," + s)
}
// 输出 jimbo,WrappedArray(nju)
test2("jimbo", "nju")

// 3.参数默认值
def test3(name: String, age: Int = 30): Unit = {
println(s"$name, $age")
}
// 如果参数传递了值,那么会覆盖默认值
test3("jimbo", 20)
// 如果参数有默认值,在调用的时候,可以省略这个参数
test3("jimbo")

// 一般情况下, 将有默认值的参数放置在参数列表的后面
def test4(sex: String = "男", name: String): Unit = {
println(s"$name, $sex")
}
// Scala 函数中参数传递是,从左到右
// test4("jimbo")

// 4.带名参数
test4(name = "jimbo")
}

5.1.5 函数至简原则

原则:能省就省

内容

  • return可以省略,Scala会使用函数体的最后一行代码作为返回值
  • 如果函数体只有一行代码,可以省略花括号
  • 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
  • 如果有return,则不能省略返回值类型,必须指定
  • 如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
  • Scala如果期望是无返回值类型,可以省略等号
  • 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
  • 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
  • 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略

案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def main(args: Array[String]): Unit = {
// 0.函数标准写法
def f(s: String): String = {
return s + "nju"
}
println(f("hello"))

// 1. return可以省略,Scala会使用函数体的最后一行代码作为返回值
def f1(s: String): String = {
s + "nju"
}
println(f1("hello1"))

// 2. 如果函数体只有一行代码,可以省略花括号
def f2(s: String): String = s + "nju"
println(f2("hello2"))

// 3. 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
def f3(s: String) = s + "nju"
println(f3("hello3"))

// 4. 如果有return,则不能省略返回值类型,必须指定
def f4(): String = {
return "hello4 nju"
}
println(f4())

// 5. 如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
def f5(): Unit = {
return "hello5 nju"
}
println(f5())

// 6. Scala如果期望是无返回值类型,可以省略等号
// 将无返回值的函数称之为过程
def f6() {
"hello6 nju"
}
println(f6())

// 7. 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def f7() = "hello7 nju"
println(f7())
println(f7)

// 8. 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f8 = "hello8 nju"
// println(f8())
println(f8)

// 9. 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def f9 = (x: String) => {
println("hello9 nju")
}
def f10(f: String => Unit) = {
f("")
}
f10(f9)
println(f10((x: String) => {
println("hello10 nju")
}))
}

image-20220920231245784

5.2 函数高级

5.2.1 高阶函数

对于一个函数,我们可以:定义函数、调用函数

但函数还有更高级的用法

  • 函数可以作为值进行传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    def main(args: Array[String]): Unit = {
    // 1.调用foo函数,把返回值给变量f
    val f = foo()
    println(f)

    // 2.在被调用函数foo后面加上 _ ,相当于把函数foo作为一个整体,传递给变量f1
    val f1 = foo _
    foo
    f1() // 不能省略()

    // 3.如果明确变量类型, 那么不使用下划线也可以将函数作为整体传递给变量
    var f2: () => Int = foo
    f2()
    }

    def foo(): Int = {
    println("foo...")
    1
    }

image-20220920232316768

  • 函数可以作为参数进行传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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 _))
    // 可以传递匿名函数
    }
  • 函数可以作为函数返回值返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def main(args: Array[String]): Unit = {
    def f1() = {
    def f2() = {
    println("f2")
    }
    f2 _
    }

    val f = f1()
    // 因为f1函数的返回值依然为函数,所以变量f可以作为函数继续调用
    f()
    // 上面代码可以简化为
    f1()()
    }

5.2.2 匿名函数

说明

没有名字的函数就是匿名函数

(x: Int) => {函数体}

传递匿名函数至简原则

  • 参数的类型可以省略,会根据形参自动推导
  • 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况: 没有参数和参数超过1的永远不能省略圆括号
  • 匿名函数如果只有一行,则大括号也可以省略
  • 如果参数只出现一次,则参数省略且后面参数可以用_代替

案例实操

单个参数版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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(","))
}

多个参数版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def main(args: Array[String]): Unit = {
def operation(a: Int, b: Int, op: (Int, Int) => Int) = {
op(a, b)
}

// 1. 标准版
println(operation(2, 3, (x: Int, y: Int) => {x + y}))

// 2. 匿名函数如果只有一行,则大括号也可以省略
println(operation(2, 3, (x: Int, y: Int) => x + y))

// 3 参数的类型可以省略,会根据形参自动推导
println(operation(2, 3, (x, y) => x + y))

// 4 如果参数只出现一次,则参数省略且后面参数可以用_代替
println(operation(2, 3, _ + _))
}

5.2.3 高阶函数案例

模拟Map映射、Filter过滤、Reduce聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def main(args: Array[String]): Unit = {
// 1. map映射
def map(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}

var arr = map(Array(1, 2, 3, 4), x => 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), _ * _)
println(arr2)
}

5.2.4 函数柯里化 & 闭包

说明

闭包: 如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包

函数柯里化: 把一个参数列表的多个参数,变成多个参数列表

案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def main(args: Array[String]): Unit = {
// 闭包
def f1() = {
val a: Int = 10
def f2(b: Int) = a + b
f2 _
}
// 在调用时, f1函数执行完毕后, 局部变量a应该随着栈空间释放掉
val f = f1()
// 但是在此处, 变量a并没有释放, 而是包含在f2函数的内部, 形成了闭合的效果
println(f(3))
println(f1()(3))

// 函数柯里化, 其实就是将复杂的参数逻辑简单化, 函数柯里化一定存在闭包
def add(a: Int) (b: Int) = {
a + b
}
println(add(10)(3))
// 实际上最先演变成def add(a: Int) = (b: Int) => a + b
// val result = add(10)
// val sum = result(3)
}

5.2.5 递归

Scala中的递归必须声明函数返回值类型

1
2
3
4
5
6
7
8
9
10
def main(args: Array[String]): Unit = {
println(test(5))
}
def test(i: Int): Int = {
if (i == 1) {
1
} else {
i * test(i - 1)
}
}

5.2.6 控制抽象

  • 值调用:把计算后的值传递进去

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def main(args: Array[String]): Unit = {
    def f = () => {
    println("f...")
    10
    }
    foo(f())
    }
    def foo(a: Int): Unit = {
    println(a)
    println(a)
    }
    // 输出结果
    // f...
    // 10
    // 10
  • 名调用:把代码块传递过去

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    def main(args: Array[String]): Unit = {
    def f = () => {
    println("f...")
    10
    }
    foo(f())
    foo({
    println("这是一个代码块")
    19
    })
    // 小括号可以省略
    foo{
    println("这是一个代码块")
    29
    }
    }
    // def foo(a: Int): Unit = {
    // => Int: 传递代码块, 且返回值为Int
    def foo(a: => Int): Unit = {
    println(a)
    println(a)
    }

    // 输出结果
    // f...
    // 10
    // f...
    // 10
    // 这是一个代码块
    // 19
    // 这是一个代码块
    // 19
    // 这是一个代码块
    // 29
    // 这是一个代码块
    // 29

案例实操

自定义while循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def main(args: Array[String]): Unit = {
var i: Int = 1
myWhile(i <= 5) {
println(i)
i += 1
}
}

def myWhile(condition: => Boolean) (op: => Unit): Unit = {
if (condition) {
op
myWhile(condition)(op)
}
}

5.2.7 惰性加载

说明

函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数

案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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被执行...")
n1 + n2
}

// 输出结果
// ---------
// sum被执行...
// res = 40

注意:lazy不能修饰var类型的变量