函数字面量
所谓函数字面量,就是用一个表达式来表示函数本身,区别于Java中的方法只能定义在类中,我们可以在任何地方定义函数字面量(类似于JavaScript),我们可以将函数字面量赋值给某个变量。或者在某个需要函数作为参数的地方,将函数字面量直接作为参数传递给函数(lambada表达式)。函数字面量或者将这种方式叫做匿名函数?。
在scala中,通常将在对象中定义的跟对象相关的函数叫做方法(method),在其它地方定义的函数叫做函数。在有些地方,他们通常混用,也不会有多大的影响。
以下是函数字面量和函数作为参数传递的示例
object TestFunc {
// (a: Int, b: Int) => a + b 是一个函数字面量,
// 将这个函数赋值给func变量
val func = (a: Int, b: Int) => a + b
// 将一个函数赋值给另外一个函数
val funcB = func
// 使用函数的变量调用函数
func(3, 5)
// 调用方法,并在某个需要传入函数作为参数的地方,传入字面量
getResult(3, 5, (a, b) => a * b)
// 定义一个方法,方法接受三个参数,前两个参数为两个数字,后面是一个函数,
// 这个方法用于获取两个参数通过指定计算函数计算之后,得到的值
// 这个示例没有什么作用,仅仅展示一种语法方式
def getResult(num1: Int, num2: Int, func: (Int, Int) => Int) = {
func(num1, num2)
}
}
局部函数
局部函数是指在某个函数或者方法内部定义函数。
在函数式编程中,程序应该被翻译成很多小的函数,这些小的函数可能只在某个函数中被定义和调用。在java中,只能定义私有方法。在Scala中可以定义局部函数(就像JavaScript)。
下面是在main方法中定义函数并调用的例子
def main(args: Array[String]): Unit = {
// 使用def定义局部函数
def func(a: Int, b: Int) = a + b
// 使用函数字面量定义函数并将之赋值给某个变量
val func1 = (a: Int, b: Int) => a + b
// 调用局部函数
func(4, 8)
func1(3, 5)
}
占位符语法
在定义函数字面量(不是通过def定义函数),为了语法简洁,可以使用占位符语法来表示一个函数
// 使用 _ 作为占位符来定义函数,_分别表示传入的第一个函数和第二个函数
val func2 = (_: Int) + (_: Int)
// 这个函数的定义和上面的函数的定义是等价的
val func3 = (a: Int, b: Int) => a + b
// 在需要一个函数作为参数传入时,使用占位符语法,使用上面的getResult函数
// 第三个参数是一个函数
getResult(3, 5, (a, b) => a * b)
// 使用占位符语法传入参数,_*_看起来像一个表达式,但其实是一个函数
getResult(3, 5, _ * _)
// 参数在函数体中被引用两次,不能使用占位符语法
val func4 = (a: Int, b: Int) => a * b + (a + b)
在使用占位符语法是,函数的参数会按照顺序依次替换_作为实际参数进行计算
在使用占位符时要注意,占位符出现的次数必须等于函数参数的个数,而且第N个占位符的值也是参数列表中第N个参数的值。如果某个函数的参数会在函数中被引用两次,则不能使用占位符语法。
可变参数
在函数定义中,可以将函数最后一个参数设置为可变数量(和JAVA中的可变参数有一点区别)
// 使用 * 定义可变参数的函数
def func5(args: Int*) = {
// args在函数内部表现为一个数组
println(args.length)
for (arg <- args) {
println(arg)
}
}
// 调用可变参数的函数
func5(6)
func5(1, 2)
func5(1, 2, 3, 4, 5)
// 如果我们已经有一个数组,并且打算将它做为参数传递给不定参数的函数时,需要使用 _* 语法
val arr = Array(7, 8, 9, 0)
// 表示传入的参数会被转换为不定参数
func5(arr: _*)
// 表示传入的参数是数组,编译不会通过
func5(arr)
在java中,如果最后一个参数是可变参数,我们可以向最后一个参数传入一个数组表示所有的参数,但这在scala中是不允许的。如果我们已经有一个数组,并且打算将它做为参数传递给不定参数的函数时,需要使用 _* 语法
参数的按名传递和参数默认值
在通常情况下,在调用函数时,参数是按照在实参列表中的位置进行传递的,在scala中,可以按名称传递参数。按名称传递时,参数的顺序不影响结果
def tf(a: Int, b: Int) = {
println(s"a=$a")
println(s"b=$b")
}
// 按位置传递参数 a=12,b=16
tf(12, 16)
tf(16, 12)
// 按名称传递参数,参数的位置不影响结果
tf(a = 12, b = 16)
tf(b = 16, a = 12)
我么可以在定义函数时,指定某些参数的默认值,在调用函数时,可以省略该参数,通常和按名称传递参数一起使用
// 定义一个带默认值的参数
def nor(a: Int = 0, b: Int = 12) = {
println(s"a=$a,b=$b")
}
// 均使用默认值
// a=0,b=12
nor()
// 按位置传递参数,第一个参数被覆盖
// a=1,b=12
nor(1)
// 按位置传递参数,并且想改写第二个参数,第一个参数不能省略
// a=1,b=100
nor(1, 100)
// 按名称传递参数,可以省略参数
// a=1,b=100
nor(b = 100)
柯里化
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
实例
首先我们定义一个函数:
def add(x:Int,y:Int)=x+y
那么我们应用的时候,应该是这样用:add(1,2)
现在我们把这个函数变一下形:
def add(x:Int)(y:Int) = x + y
那么我们应用的时候,应该是这样用:add(1)(2),最后结果都一样是3,这种方式(过程)就叫柯里化。
柯里化的意义。
当我们将函数作为参数传入时,可以实现一种新的控制结构,如下所示。
import java.io.PrintWriter
object TestApp {
// 定义一个柯里化函数,向指定路径写入文件,并关闭流
def withPrintWriter(path: String)(func: PrintWriter => Unit) = {
val writer = new PrintWriter(path)
try {
func(writer)
} finally {
writer.close()
}
}
def main(args: Array[String]): Unit = {
withPrintWriter("d:/out.txt") {
printer => {
printer.println("good good study")
}
}
}
}