函数式编程
- 概念
- 为完成某一功能的程序语句的集合,称为函数
- 类中的函数称之为方法
实例
- Scala可以在任何语法结构中声明任何的语法
- 函数没有重载和重写的概念;方法可以进行重载和重写
- Scala中函数可以嵌套定义
Scala中方法参数是val, 任何试图重新赋值都会失败 ```scala Object Test { def main(args: Array[String]): Unit = { // 定义函数 def sayHi(name: String): Unit = { println(“hi “ + name) } //调用函数。如果没有上面的函数,则会调用下面的方法 sayHi(“alice”) // hi alice
// 调用对象的方法 Test.sayHi(“bob”) // HI bob
// 获取方法的返回值 val result = Test.sayHello(“Tom”) println(result) // Hello Tom // Hello
}
// 定义对象的方法 def sayHi(name: String): Unit = { println(“HI “ + name) }
def sayHello(name: String): String = { println(“Hello “ + name) return “Hello” } } ```
函数定义
```scala // 无参无返 def f1(): Unit = { println(“无参无返”) }
//无参有返 def f2(): Int = { println(“无参有返”) return 12 }
// 有参无返 def f3(name: String): Unit = { println(“有参无返” + name) }
// 有参有返 // 多参无返 // 多参有返
<a name="Q93T8"></a>
### 函数参数
```scala
object Test {
def main(args: Array[String]): Unit = {
可变参数
def f1(str: String*): Unit = {
println(str)
}
f1("aaa","bbb") //WrappedArray(aaa,bbb)
// 如果多个参数,可变参数放在最后
默认参数
// 参数默认值,一般将有默认值的参数放置在参数列表的后面
// 给name赋了默认值xxx
def f3(name: String = "xxx"): Unit = {
println(name)
}
f3() // xxx
f3("kkk") // kkk
带名参数
def f4(name: String = "xxx", age: Int): Unit = {
println(s"${age}岁的${name}在学习")
}
f4("Tom",20) // 20岁的Tom在学习
f4(age = 21)// 21岁的xxx在学习
}
}
函数调用法
// 后缀调用法
Math.abs(-10)
// res0: Int = 10
// 中缀调用法 方法只有一个参数,调用省略.和括号
Math abs -20
1 + 1 // 操作符也是方法
//当方法以:结尾,调用者在后面
1 :: list 可以写成 list.::1
// 花括号调用法 只有一个参数的时候,使用花括号调用法
Math.abs{
println("求绝对值")
-40
}
// 无括号调用 方法无返回值,参数列表为空
def sayHello() = println("Hello, scala")
sayHello
函数至简原则
return可以省略,Scala会使用函数体最后一行的代码作为表达式的返回值
def f1(name: String): String = {
name
}
如果函数体只有一行代码,可以省略花括号
def f2(name: String): String = name
返回值类型如果可以推断出来,那么可以省略
def f3(name: String) = name // 变成了f(x) = x
如果有return,就不能省略返回类型
- 如果是递归方法不能省略返回值
- 如果函数明确声明Unit,那么即使函数体中使用了return也不起作用
- 如果期望是无返回值类型,可以省略等号
如果声明为Unit,即使返回值为其它类型,也会被丢掉
def f6(name: String) {
println(name)
}
如果函数无参,但是声明了参数列表,那么调用时小括号可加可不加
def f7(): Unit = {
println("xxx")
}
f7
如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f8: Unit = {
println("xxx")
}
f8
如果不关心名称,只关心处理逻辑,那么函数名def可以省略
// 匿名函数,lambda表达式
(name: String) => {println(name)}
函数高级
匿名函数
没有名字的函数就是匿名函数
val 函数变量名 = (x: Int) => (函数体)
- Scala中函数是对象
- 类似方法,也有参数列表返回值
- 不需要def
- 无需指定返回类型
object Test {
def main(args: Array[String]): Unit = {
val fun = (name: String) => {println(name)}
fun("xxx")
}
}
方法和函数的区别
- 方法隶属于类或者对象,运行时加载到JVM方法区中
- 函数对象赋值给一个变量,运行时加载到JVM堆内存中
- 函数是一个对象,继承自FunctionN,函数对象有apply,curried,toString,tupled这些方法。方法则没有
- 函数是对象,而方法属于对象
// 函数
val a = (x: Int, y: Int) => x + y
// 方法
def b(x: Int, y: Int) = x + y
val c = b // error 没法直接把方法付给变量
将方法转换为函数
def b(x: Int, y: Int) = x + y
val c = b _
val sum = c(10, 20) //30
定义一个函数,以函数作为参数输入
```scala // f的参数:func是以String为参数类型,Unit为返回值的函数! def f(func: String => Unit): Unit = { func(“NJU”) }
// 调用 将函数fun作为参数调入func f(fun) // xxx
// 也可以写成 f( (name: String) => {println(name)} ) // xxx
/ 定了数据,传入一个操作 /
<a name="Wk9m5"></a>
#### 匿名函数的简化原则(调用时)
- 参数的类型可以省略,函数定义时参数类型已经写死类,会根据形参进行自动的推导
```scala
f( (name) => {println(name)} )
类型省略之后,发现只有一个参数,则括号可以省略。没有参数或者多于一个时,不能省略括号
f( name => {println(name)} )
匿名函数如果只有一行,大括号可以省略
f( name => println(name) )
如果参数只出现一次,则参数可以省略,且后面参数可以用_代替
f( println(_) )
如果可以推断出当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线
f( println )
实际示例
```scala // 实际示例,定义一个“二元运算符”函数,只操作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( + )) println(dualFunctionOneAndTwo( - ) println(dualFunctionOneAndTwo(- + ) // b-a
<a name="ZfTBp"></a>
### 高阶函数
对于一个函数,我们可以定义函数、调用函数
```scala
object Test {
def main(args: Array[String]): Unit = {
def f(n: Int): Int = {
println("f调用")
n + 1
}
def fun(): Int = {
println("fun调用")
1
}
}
val result: Int = f(123)
println(result) // f调用 124
函数可以作为值传递(不是函数调用,函数调用是计算出值传过来)
def add(a: Int, b: Int) = a + b
val a = add // error
val a = add _ // a: (Int, Int) => Int = <function>
val f1: Int=>Int = f // f1返回值类型为(Int=>Int),表示指向的是函数
val f2 = f _ // f _表示本身这个函数
println(f1) // 返回一串引用,一个函数对象
println(f1(12)) // f调用 13
println(f2) //和f1指向不一样
val f3 = fun
val f4 = fun _
val f5 : ()=>Int = fun
println(f3) // fun调用 1
println(f4) // 函数对象
println(f5) // 调用对象
}
}
函数可以作为参数传递
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, 1, 2))
//也可以写成匿名函数
println(dualEval((a, b) => a + b, 1, 2))
// 简写
println(dualEval(_ + _, 12, 35))
函数可以作为函数返回值返回
def f1(): Int=>Unit = { // 返回值定义为函数形式
def f2(a: Int): Unit = {
println("f2调用 " + a)
}
f2 // 将函数直接返回,而不是调用
}
println(f1()) // 得到的是一个函数对象
println(f1()(23)) // 输出:f2调用 23
实例
object Test {
def main(args: Array[String]): Unit = {
val arr: Array[Int] = Array(12,23,44,55)
// 对数组进行不同对操作,返回新的数组
def arrayOperation(array: Array[Int], op: Int=>Int): Array[Int] = {
for (elem <- array) yield op(elem)
}
// 定义一个加一操作
def addOne(elem: Int): Int = {
elem + 1
}
// 调用函数
val newArray: Array[Int] = arrayOperation(arr, addOne)
println(newArray.mkString(",")) // 每个元素按逗号分隔开
// 传入匿名函数实现元素翻倍
val newArray2 = arrayOperation(arr, elem = elem * 2)
// 简写
val newArray2 = arrayOperation(arr, _ * 2)
}
}
练习1
object Test1 {
def main(args: Array[String]): Unit = {
// 定义一个匿名函数赋值给fun,有三个参数,返回值类型为Boolean
val fun = (i: Int, s: String, c: Char) => { if(i==0 && s== "" && c== '0') false else true}
println(fun(0, "", '0')) //false
println(fun(0, "", '1')) // true
}
}
练习2
object Test2 {
def main(args: Array[String]): Unit = {
// 定义一个函数func,它接收一个Int类型参数,返回一个函数f1。f1接受一个String类型参数,返回一个函数f2.f2接收一个Char类型参数,返回Boolean
def func(i: Int): String=>(Char=>Boolean) = {
def f1(s: String): Char=>Boolean = {
def f2(c: Char): Boolean = {
if(i==0 && s== "" && c== '0') false else true
}
f2
}
f1
}
println(func(0)("")('0'))
}
}
//匿名函数简写
def func(i: Int): String=>(Char=>Boolean) = {
s => c => if(i==0 && s== "" && c== '0') false else true
}
println(func(0)("")('0'))
// 柯里化
def func2(i: Int)(s: String)(c: Char): Boolean = {
if (i==0 && s == "" && c =='0') false else true
}
函数柯里化&闭包
- 闭包:如果一个函数,访问到了外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包。(Scala在栈空间存放函数的同时,会在堆中创建函数对象)
函数柯里化: 把一个参数列表的多个参数,变成多个参数列表
object Test {
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 addByA(a: Int): Int=>Int = {
def addB(b: Int): Int = {
a + b
}
addB
}
// 内层和外层打包到一起
println(addByA(35)(24)) // 35 + 24
val addByFour2 = addByA(2)
println(addByFour2(3)) // 2 + 3
// lambda表达式简写
def addByA1(a: Int): Int=>Int = a + _
// 柯里化
def addCurrying(a: Int)(b: Int): Int = {
a + b
}
}
```scala def merge(a: String, b: String)(f: (String, String) => String) = f(a, b)
val str1 = merge(“abc”,”efg”)( + ) val str2 = merge(“abc”,”efg”)(.toUpperCase() + ) //不需要修改函数体,就可以改变操作
<a name="e6YXd"></a>
### 递归
- 定义递归方法不能自动推断,必须有返回值的数据类型
- 递归必须有出口否则就是死递归
- 构造方法不能递归
- 如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
- 尾递归的原理: 当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
```scala
// Java
public class Test {
public static void main(String[] args) {
System.out.println(factorial(5))
}
// 循环
public static int factorials1(int n){
int result = 1;
for(int i=1; i <= 5; i++){
result *= i;
}
return result;
}
// 递归
public static int factorial2(int n){
if (n == 0)
return 1;
return n * factorial2(n-1);
}
}
// Scala中递归
object Test {
def main(args: Array[String]): Unit = {
println(fact(5))
}
def fact(n: Int): Int = {
if (n == 0) 1
else fact(n-1) * n
}
// 尾递归方式
// Scala在栈中会使用覆盖支持尾递归
def tailFact(n: Int): Int = {
@tailrec
def loop(n: Int, result: Int): Int = {
if (n == 0) return result
loop(n - 1, result * n)
}
loop(n, 1)
}
}
控制抽象
- 值调用:把计算后的值传递过去
- 名调用:把代码传递过去 ```scala val myShop = (f1:() => Unit) => { println(“welcome”) f1() println(“欢迎下次光临”) }
myShop{ // 匿名函数,无参无返 () => { println(“我要买手机”) } }
```scala
object Test {
def main(args: Array[String]): Unit = {
// 1. 传值参数
def f0(a: Int): Unit = {
println("a :" + a)
}
def f1(): Int = {
println("f1调用")
12
}
f0(f1()) // f1 调用 12
// 2. 传名参数,传递不是具体值,是代码块
def f2(a: =>Int): Unit = {
println("a: " + a)
println("a: " + a)
}
f2(23) //a: 23
f2(f1()) // 返回两次 “f1调用 a: 12”,相当于把f2中的a替换成了f1()调用了两次
}
}
惰性加载
- 当函数被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行
lazy不能修饰var
object Test {
def main(args: Array[String]): Unit = {
lazy val result: Int = sum(13, 47)
println("1. 函数调用")
println("2. result = " + result)
}
def sum(a: Int, b: Int): Int = {
println("3. sum调用")
a + b
}
}
/*
1.
3.
2.
*/
调用法
后缀调用
Math.abs(-10)
中缀调用
Math abs -10
1 + 1
1 to 10
花括号调用法
Math.abs{
println("求绝对值")
-20
}
无括号调用
如果没有参数,可以省略方法名后面的括号
def sayHello = println("hello")
sayHello
scala 中返回类型为Unit的方法称为过程,=可以不写
def sayHello() {println("hello")} // 花括号不能省略