[toc]
## Scala函数式编程高级
> 先看一个需求:
>
> 给你一个集合`val list = List(1, 2, 3, 4, "abc")` ,请完成如下要求:
>
> 1. 将集合list中的所有数字+1,并返回一个新的集合。
> 2. 要求忽略掉非数字的元素,即返回的新的集合形式为 `(2, 3, 4, 5)`
>
> 解决方式:
>
> 1. filter和map返回新的集合
> 2. 模式匹配
~~~~scala
package com.atguigu.chapter13
/**
* @Date 2021/4/3 20:21
* @Version 10.21
* @Author DuanChaojie
*/
object PartialFunDemo01 {
def main(args: Array[String]): Unit = {
/**
* 给你一个集合val list = List(1, 2, 3, 4, "abc") ,请完成如下要求:
* 将集合list中的所有数字+1,并返回一个新的集合
* 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)
*/
/**
* 思路1 filter + map 方式解决
* 虽然可以解决问题,但是麻烦.
*/
val list = List(1, 2, 3, 4, "hello")
/** 方案一:
* val res1 = list.filter(filterFun)
* println("res1 = " + res1)
*
* val res2 = res1.map(anyToInt)
* println("res2 = " + res2)
*
* val res3 = res2.map(funAddOne)
* println("res3 = " + res3)
*/
val res1 = list.filter(filterFun).map(anyToInt).map(funAddOne)
println(res1)
/**
* 解决方案二:使用模式匹配
*/
val res2 = list.map(addOne)
println(res2)
}
def addOne(x: Any): Any = {
x match {
case n: Int => n + 1
case _ =>
}
}
/**
* @param x 输入的类型为Any类型
* @return 输入的x为Int类型才返回true
*/
def filterFun(x: Any): Boolean = {
x.isInstanceOf[Int]
}
/**
* @param n Any类型的参数
* @return 返回Int
*/
def anyToInt(n: Any): Int = {
n.asInstanceOf[Int]
}
/**
* @param n
* @return n + 1
*/
def funAddOne(n: Int): Int = {
n + 1
}
}
~~~~
### 1. 偏函数
> 1. 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择。
> 2. 将包在大括号内的一组case语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出范围的值会忽略(未必会忽略,这取决于你打算怎样处理)
> 3. 偏函数在Scala中是一个`特质PartialFunction`
> 4. 使用偏函数解决前面的问题:
~~~scala
/**
* @Date 2021/4/3 20:36
* @Version 10.21
* @Author DuanChaojie
*/
object PartialFunDemo02 {
def main(args: Array[String]): Unit = {
//使用偏函数解决
val list = List(1, 2, 3, 4, "hello")
/**
* 定义一个偏函数
* 1. PartialFunction[Any,Int] 表示偏函数接收的参数类型是Any,返回类型是Int
* 2. isDefinedAt(x: Any) 如果返回true ,就会去调用 apply 构建对象实例,如果是false,过滤
* 3. apply 构造器 ,对传入的值 + 1,并返回(新的集合)
*/
val partialFun = new PartialFunction[Any, Int] { // PartialFunction是一个特质
override def isDefinedAt(x: Any): Boolean = {
x.isInstanceOf[Int]
}
override def apply(v1: Any): Int = {
v1.asInstanceOf[Int] + 1
}
}
/**
* 说明:如果是使用偏函数,则不能使用map,应该使用collect
* 说明一下偏函数的执行流程
* 1. 遍历list所有元素
* 2. 然后调用 val element = if(partialFun-isDefinedAt(list单个元素)) {partialFun-apply(list单个元素) }
* 3. 每得到一个 element,放入到新的集合,最后返回
*/
val res = list.collect(partialFun)
// List(2, 3, 4, 5)
println(res)
}
}
~~~
> 偏函数小结:
>
> 1. 使用构建特质的实现类(使用的方式是PartialFunction的匿名子类)
> 2. PartialFunction 是个特质(看源码)
> 3. 构建偏函数时,参数形式 [Any, Int]是泛型,第一个表示参数类型,第二个表示返回参数
> 4. 当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行isDefinedAt()如果为true ,就会执行 apply, 构建一个新的Int 对象返回。
> 5. 执行isDefinedAt() 为false 就过滤掉这个元素,即不构建新的Int对象。
> 6. map函数不支持偏函数,因为map底层的机制就是所有循环遍历,无法过滤处理原来集合的元素
> 7. `collect函数支持偏函数。`
#### 偏函数简化形式
`声明偏函数,需要重写特质中的方法,有的时候会略显麻烦,而Scala其实提供了简单的方法`
~~~~scala
/**
* @Date 2021/4/3 20:47
* @Version 10.21
* @Author DuanChaojie
*/
object PartialFunDemo03 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, "hello")
/**
* 第一种简写方式:使用case
* @return
*/
def partialFun1: PartialFunction[Any, Int] = {
case i: Int => i + 1
}
val res1 = list.collect(partialFun1)
//res1 = List(2, 3, 4, 5)
println("res1 = " + res1)
/**
* 第二种简写方式:
*/
val res2 = list.collect {
case i: Int => i + 1
}
// res2 = List(2, 3, 4, 5)
println("res2 = " + res2)
}
}
~~~~
#### 作为参数的函数
函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:`function1`,即:`(参数类型) => 返回类型`
~~~~scala
/**
* @Date 2021/4/3 20:55
* @Version 10.21
* @Author DuanChaojie
*/
object FunParameterDemo01 {
def main(args: Array[String]): Unit = {
def plus(x: Int) = 3 + x
//val res = Array(3,6,9).map(plus(_))
val res = Array(3,6,9).map(plus)
// mkString(seq:String)方法是将原字符串使用特定的字符串seq分割。
// 6,9,12
println(res.mkString(","))
/**
* 在scala中,函数也是有类型,比如plus就是<function1>
*/
println(plus _)
}
}
~~~~
> 小结:
>
> 1. `map(plus(_))` 中的`plus(_)` 就是将plus这个函数当做一个参数传给了map,`_这里代表从集合中遍历出来的一个元素。`
> 2. `plus(_)` 这里也可以写成 plus 表示对 Array(1,2,3,4) 遍历,将每次遍历的元素传给plus的 x
> 3. 进行 3 + x 运算后,返回新的Int ,并加入到新的集合 res中
> 4. `def map[B, That](f: A => B)` 的声明中的 `f: A => B` 一个函数
### 2. 匿名函数
> 没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数。
~~~~scala
/**
* @Date 2021/4/3 21:02
* @Version 10.21
* @Author DuanChaojie
*/
object AnonymousFunDemo01 {
def main(args: Array[String]): Unit = {
/**
* 对匿名函数的说明
* 1. 不需要写def函数名
* 2. 不需要写返回类型,使用类型推导
* 3. =变成=>
* 4. 如果有多行,则使用{} 包括
*/
val triple = (x: Double) => {
println("x = " + x)
x * x
}
// triple(3) = 9.0
println("triple(3) = " + triple(3))
}
}
~~~~
> 课堂案例:请编写一个匿名函数,可以返回2个整数的和,并输出该匿名函数的类型
~~~~scala
/**
* @Date 2021/4/3 21:41
* @Version 10.21
* @Author DuanChaojie
*/
object AnonymousFunDemo02 {
def main(args: Array[String]): Unit = {
val sum = (n1: Int, n2: Int) => {
n1 + n2
}
// 36
println(sum(21, 15))
// sum函数的类型是:<function2>
println("sum函数的类型是:" + sum)
}
}
~~~~
### 3. 高阶函数
> ==能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)==。可使应用程序更加健壮。
~~~scala
/**
* @Date 2021/4/3 21:44
* @Version 10.21
* @Author DuanChaojie
*/
object HigherOrderFunDemo01 {
def main(args: Array[String]): Unit = {
val res = hfun(fun1, fun2, 3.5)
println("res = " + res)
}
/**
* 高阶函数
* @param f1
* @param f2
* @param n
* @return 将n进行以下处理返回:(n+n).toInt
*/
def hfun(f1: Double => Double, f2: Double => Int, n: Double) = {
f2(f1(n))
}
/** 普通函数fun1
* @param x 输入Double类型的值
* @return x + x
*/
def fun1(x: Double): Double = {
x + x
}
/** 普通函数fun2
* @param x 输入Double类型的值
* @return 返回对应的Int
*/
def fun2(x: Double): Int = {
x.toInt
}
}
~~~
> 高阶函数可以返回函数类型
~~~~scala
/**
* @Date 2021/4/3 21:52
* @Version 10.21
* @Author DuanChaojie
*/
object HigherOrderFunDemo02 {
def main(args: Array[String]): Unit = {
/**
* 这里返回的fun1就是minusxy里面的匿名函数:(y: Int) => 3 + y
* 等价于 def fun1 = (y: Int) => 3 + y
* 所以fun1(6)的结果为9
* 也可以一步到位:minusxy(3)(6)
*/
val fun1 = minusxy(3)
// fun1 = <function1>
println("fun1 = " + fun1)
val res = fun1(6)
// res = 9
println("res = " + res)
// minusxy(3)(6) = 9
println("minusxy(3)(6) = " + minusxy(3)(6))
}
def minusxy(x: Int) = {
// 匿名函数
(y: Int) => x + y
}
}
~~~~
### 4. 类型推断
> 参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如`list=(1,2,3) list.map()` map中函数参数类型是可以推断的),同时也可以进行相应的简写。
>
> 参数类型推断写法说明:
>
> 1. `参数类型是可以推断时,可以省略参数类型`
> 2. `当传入的函数,只有单个参数时,可以省去括号`
> 3. `如果变量只在=>右边只出现一次,可以用_来代替`
~~~~scala
/**
* @Date 2021/4/3 22:04
* @Version 10.21
* @Author DuanChaojie
*/
object ParameterInferDemo {
/**
* 参数类型是可以推断时,可以省略参数类型
* 当传入的函数,只有单个参数时,可以省去括号
* 如果变量只在=>右边只出现一次,可以用_来代替
*/
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
println(list.map((x: Int) => x + 1)) // List(2, 3, 4, 5)
println(list.map((x) => x + 1)) //List(2, 3, 4, 5)
println(list.map(x => x + 1)) //List(2, 3, 4, 5)
println(list.map(_ + 1)) //List(2, 3, 4, 5)
println(list.reduce(fun1)) // 10
println(list.reduce((n1: Int, n2: Int) => n1 + n2)) //10
println(list.reduce((n1, n2) => n1 + n2)) //10
println(list.reduce(_ + _)) //10
}
def fun1(n1: Int, n2: Int): Int = {
n1 + n2
}
}
~~~~
> 1. `map是一个高阶函数,因此也可以直接传入一个匿名函数,完成map`
> 2. `当遍历list时,参数类型是可以推断出来的,可以省略数据类型Int println(list.map((x)=>x + 1))`
> 3. `当传入的函数,只有单个参数时,可以省去括号 println(list.map(x=>x + 1))`
> 4. `如果变量只在=>右边只出现一次,可以用_来代替 println(list.map(_ + 1))`
### 5. 闭包
闭包就是==一个函数==和==与其相关的引用环境==组合的一个==整体==(实体)。
通过一个例子了解闭包:
~~~~scala
object Demo {
def main(args: Array[String]): Unit = {
val fun1 = minusxy(3)
val res1 = fun1(6)
val res2 = fun1(9)
}
def minusxy(x: Int) = {
// 匿名函数
(y: Int) => x + y
}
}
~~~~
> 1. `(y: Int) => x + y`返回的是一个匿名函数 ,因为该函数引用到到函数外的 x,那么该函数和x整体形成一个闭包如:`这里 val fun1 = minusxy(3) 的fun1函数就是闭包`
> 2. 你可以这样理解,返回函数是一个对象,而x就是该对象的一个字段,他们共同形成一个闭包
> 3. 当多次调用fun1时(可以理解多次调用闭包),发现使用的是同一个x, 所以x不变。
> 4. 在使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为他们会组合成一个整体(实体),形成一个闭包。
#### 闭包的最佳实践
> 请编写一个程序,具体要求如下:
>
> 1. 编写一个函数 `makeSuffix(suffix: String)` 可以接收一个文件后缀名(比如.png),并返回一个闭包
> 2. 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.png) ,则返回 文件名.png, 如果已经有`.png`后缀,则返回原文件名。
> 3. 要求使用闭包的方式完成
> 4. `String.endsWith(xx)`
~~~~scala
/**
* @Date 2021/4/3 22:13
* @Version 10.21
* @Author DuanChaojie
*/
object ClosureDemo01 {
def main(args: Array[String]): Unit = {
val fun = makeSuffix(".png")
// dog.png
println(fun("dog.png"))
// cat.png
println(fun("cat"))
}
/**
* @param suffix 传入
* @return
*/
def makeSuffix(suffix: String) = {
// 返回一个匿名函数,会使用到suffix
(filename: String) => {
if (filename.endsWith(suffix)) {
filename
} else {
filename + suffix
}
}
}
}
~~~~
> 体会闭包的好处:
>
> 1. 返回的匿名函数和 makeSuffix (suffix string) 的 suffix 变量 组合成一个闭包,因为返回的函数引用到suffix这个变量
> 2. 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入 后缀名,比如 .png ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。大家可以仔细的体会一把。
### 6. 函数柯里化(curry)
> 1. `函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化。`
> 2. 柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里化操作。
> 3. 不用设立柯里化存在的意义这样的命题。柯里化就是以函数为主体这种思想发展的必然产生的结果。(即:柯里化是面向函数思想的必然产生结果)
#### 函数柯里化快速入门
> 编写一个函数,接收两个整数,可以返回两个数的乘积,要求:
>
> 1. 使用常规的方式完成
> 2. 使用闭包的方式完成
> 3. 使用函数柯里化完成
```scala
/**
* @Date 2021/4/3 22:20
* @Version 10.21
* @Author DuanChaojie
*/
object CurryDemo01 {
def main(args: Array[String]): Unit = {
// 使用常规的方式完成
def curry1(x: Int, y: Int) = x * y
println(curry1(10, 10))
// 使用闭包的方式完成
def curry2(x: Int) = (y: Int) => x * y
println(curry2(10)(10))
// 使用函数柯里化完成
def curry3(x: Int)(y: Int) = x * y
println(curry3(10)(10))
}
}
```
#### 函数柯里化最佳实践
> 比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
>
> 1. 全部转大写(或小写)
> 2. 比较是否相等
>
> 针对这两个操作,我们用一个函数去处理的思想,其实也变成了两个函数处理的思想(柯里化)
```scala
/**
* @Date 2021/4/3 22:23
* @Version 10.21
* @Author DuanChaojie
*/
object CurryDemo02 {
def main(args: Array[String]): Unit = {
val str = "ddaimm"
// false
println(str.compareStr("HELLO")(fun))
// true
println(str.compareStr("ddaimm")(_.equals(_)))
}
/**
* 可以接收两个字符串,比较是否相等
*/
def fun(str1: String, str2: String): Boolean = {
str1.equals(str2)
}
/**
* 隐式类
*/
implicit class compare(str1: String) {
/**
* 体现了将比较字符串的事情,分解成两个任务完成
* 1. compareStr 转换大小写
* 2. fun函数完成比较任务
*/
def compareStr(str2: String)(fun: (String, String) => Boolean): Boolean = {
fun(str2.toUpperCase(), str1.toUpperCase())
}
}
}
```
### 7. 控制抽象
> `如何实现将一段代码(从形式上看),作为参数传递给高阶函数,在高阶函数内部执行这段代码.,其使用的形式如 breakable{} 。`
~~~~scala
var n = 10
breakable {
while (n <= 20) {
n += 1
if (n == 18) {
break()
}
}
}
~~~~
> 控制抽象是这样的函数,满足如下条件
>
> 1. `参数是函数`
> 2. `函数参数没有输入值也没有返回值`
~~~~scala
/**
* @Date 2021/4/3 22:37
* @Version 10.21
* @Author DuanChaojie
*/
object AbstractControlDemo01 {
def main(args: Array[String]): Unit = {
/**
* myRunInThread 就是一个抽象控制
* 是没有输入, 也没有输出的函数 f1: () => Unit
*/
def myRunInThread(f1: () => Unit) = {
new Thread {
override def run(): Unit = {
f1() //只写了 f1
}
}.start()
}
myRunInThread { () => {
println("mm干活咯!5秒完成...")
Thread.sleep(5000)
println("mm干完咯!")
}
}
//简写形式
def myRunInThread2(f1: => Unit) = {
new Thread {
override def run(): Unit = {
f1 //只写了 f1
}
}.start()
}
// 对于没有输入,也没有返回值函数,可以简写成如下形式
myRunInThread2 {
println("dd干活咯!5秒完成...")
Thread.sleep(5000)
println("dd干完咯!")
}
}
}
~~~~
> `进阶用法:实现类似while的until函数`
~~~~scala
/**
* @Date 2021/4/3 22:44
* @Version 10.21
* @Author DuanChaojie
*/
object AbstractControlDemo02 {
def main(args: Array[String]): Unit = {
var x = 10
until(x == 0) {
x -= 1
println("x=" + x)
}
}
/** 说明
* 1 函数名为 until , 实现了类似 while循环的效果
* 2. condition: => Boolean 是后一个没有输入值,返回Boolean类型函数
* 3. block: => Unit 没有输入值,也没有返回值的韩
*/
def until(condition: => Boolean)(block: => Unit): Unit = {
//类似while循环,递归
if (!condition) {
block // x= 9 ,x = 8 x =7 ....
until(condition)(block)
}
}
}
~~~~
## ☆