函数式编程
1、前言
面向对象编程
解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题
对象:用户
行为:登录、连接JDBC、读取数据库
属性:用户名、密码
Scala语言是一个**完全面向对象编程语言。**真正的万物皆对象(Java语言不是)
对象的本质:对数据和行为的一个封装
函数式编程
解决问题时,将问题分解称为一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
2、函数基础
2.1、函数基本语法
基本语法:
/**
* def: 定义函数关键字
* def 函数名(参数名: 参数类型, 参数名: 参数类型): 函数返回值类型 {
* 函数体
* }
*/
def sum(x: Int, y: Int): Int = {
x + y
}
2.2、函数和方法的区别
核心概念:
Scala有方法与函数,二者再语义上的区别很小。Scala中方法是类的一部分,而函数是一个对象可以赋值给一个变量
Scala中的函数是一个完整的对象,Scala中的函数其实就是集成了Trait
的类的对象
Scala中使用val
定义函数,使用def
定义方法
例:
def main(args: Array[String]): Unit = {
val func = (a: Int, b: Int) => a + b
}
上述代码定义了main
方法与func
函数
也就是说,除了作用域与名字不同外,就没啥差别了对吧
2.3、函数定义
Scala中方法声明格式:
def 函数名([参数列表]) : [返回值类型] = {
方法体
返回值
}
如果你不写等于号和方法主体,那么方法会被隐式声明为抽象(abstract),包含它的类型于是也是一个抽象类型。
例:
def sum(a: Int, b: Int): Int = {
a + b
}
如果方法没有返回值,返回值类型可以定义为 Unit,这个类似于 Java 的 void
2.4、函数参数
- 可变参数
- 如果参数列表中存在多个参数,那么可变参数一般放在最后
- 参数默认值,一般将有默认值的参数放置在参数列表的后面
- 带名参数
2.5、函数至简原则
- return可以省略,Scala会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略
- 如果有return,则不能省略返回值类型,必须指定
- 如果函数明确声明unit,那么即使函数体中使用了return关键字,也不起作用
- Scala如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加,可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时,小括号必须省略
- 如果不关心名称,只关系逻辑处理,那么函数名可以省略
2.6、匿名函数
匿名函数至简原则
- 参数的类型可以省略,Scala根据形参自动推导
- 类型省略后,仅有一个参数,则可以省略圆括号
- 匿名函数如果只有一行,则大括号可以省略
- 如果参数只出现一次,则参数省略且后面参数可以用下划线
_
代替 - 如果可以推导出当前传入的println是一个函数体,而不是调用语句,可以省略下划线_ (对应Java语言的方法引用?)
2.7、函数传名调用(call-by-name)
Scala 的解释器在解析函数参数(function arguments)时有两种方式:
- 传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部
- 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部
在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的
即:每次使用传名调用时,解释器都会计算一次表达式的值
/**
* 函数传名调用
*/
def callByName_demo(): Unit = {
def pr() = {
println("merlin")
}
def call(t: () => Unit): Unit = {
println("function call")
t()
}
call(pr)
}
/**
* 这种方式好像也是跟上面是一样的
*/
def callByName_demo2(): Unit = {
def pr() = {
println("merlin")
}
def call(t: => Unit): Unit = {
println("function call")
t
}
call(pr())
}
2.8、偏应用函数
Scala偏应用函数是一种表达式,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数
例:
/**
* 偏函数写法
*/
def partialFunctions(): Unit = {
val num = 3
val p3 = plus(num, _: Int, _: Int)
val result1 = p3(1, 2)
val result2 = p3(2, 3)
val result3 = p3(3, 4)
println(s"result1=${result1}, result2=${result2}, result3=${result3}")
}
/**
* 基础写法
*/
def baseFunction(): Unit = {
val num = 3
val result1 = plus(num, 1, 2)
val result2 = plus(num, 2, 3)
val result3 = plus(num ,3 ,4)
println(s"result1=${result1}, result2=${result2}, result3=${result3}")
}
def plus(a: Int, b: Int, c: Int) = a + b + c
多参函数调用时,当部分参数确定,则可以使用偏函数进行优化:绑定已经确定的参数,再使用下划线_替换缺失的参数列表,并把这个新函数值的索引赋给变量
2.9、闭包
闭包是指:一个函数的函数体中依赖函数外定义的变量
/**
*
* 这样定义的函数变量 sum 成为一个"闭包"
* 因为它引用到函数外面定义的变量num
* 定义这个函数的过程是将这个自由变量捕获而构成一个封闭的函数
*/
def closures_demo(): Unit = {
val num = 3
//num 为函数外定义的变量
val sum = (a: Int) => a + num
val result = sum(1)
println(result)
}
2.10、函数柯里化(Function Currying)
函数柯里化指的是将原来接受两个参数的函数变成新的接受一个参数的过程。新的函数返回一个以原有第二个参数未参数的函数。
def main(args: Array[String]): Unit = {
//直接使用
val result1 = curryingSum(1)(2)
println(result1)
//拆分使用
val result2: Int => Int = curryingSum(1)
val result3: Int = result2(2)
println(result3)
}
/**
* 普通函数
*/
def sum(a: Int, b: Int) = a + b
/**
* 柯里化函数
*/
def curryingSum(a: Int)(b: Int) = a + b
curryingSum(1)(2)
实际上是连续调用了两个普通函数
2.11、递归
基本递归调用:与Java语言一致
- 递归调用实现代码较为简洁,可读性较好
- 但跟Java一样,递归调用会占用大量的栈空间,当递归基数过大,会造成
StackOverFlow
异常
def recursion_demo(): Unit = {
def func(n: Int) : Int = {
if(n == 0) return 1
func(n - 1) * n
}
println(func(4))
}
尾递归调用:
- 尾递归是递归调用的特殊实现。尾递归函数中,最后一行代码语句就是递归函数调用本身,而且其结果被直接返回
- 尾递归的实现:每次函数调用时,都会抛弃上次函数调用时栈帧中的内容
def recursion_demo2(): Unit = {
def func(n: Int, currRes: Int): Int = {
if (n == 0) return currRes
func(n - 1, currRes * n)
}
println(func(4, 1))
}
2.12、惰性加载
函数返回值被声明为lazy
时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。
def main(args: Array[String]): Unit = {
lazy_load_demo()
}
def lazy_load_demo(): Unit ={
lazy val result = sum(23, 37)
println("函数调用")
println(s"函数返回结果为:${result}")
}
def sum(a: Int, b:Int) : Int = {
println("sum 函数执行")
a + b
}