Scala是一门多范式编程语言,函数式编程是Scala的一种重要特性
最基本的Scala函数是可重用的命名表达式,定义函数使用def关键字:def 函数名(参数列表):返回值类型 = {函数体}
定义无输入的函数
例如:def hi = {"hi"} 或 def hi() = {"hi"}
定义没有参数的函数时括号是可选择的。如果定义时有括号,则调用时的括号也是可选择的;但如果定义时没有括号,调用时一定不能用括号。
定义函数时指定返回类型:def hi():String = {"hi"}
定义函数
函数的函数体基本上由表达式或表达式块组成,在这里最后一行将成为表达式的返回值,相应地也就是函数的返回值。
有些情况可能需要在函数的表达式块结束前退出并返回一个值,可以采用return关键字来指定函数的返回值,然后退出函数。
定义函数时参数必须加类型
定义函数时指定参数
def info(name:String,age:Int):String = {
s"${name}今年${age}岁"
}
用命名参数调用函数
调用函数的惯例是按原先定义时的顺序指定参数。在Scala中,还可以按名调用参数,这样就允许不按顺序指定参数。
举例:
def greet(prefix:String,name:String) = s"$prefix $name"
def main(args: Array[String]): Unit = {
println(greet("Ms","Brown"))
println(greet(name="Brown",prefix="Ms"))
}
定义参数的默认值
定义参数默认值后,调用函数时仍可以指定参数的值,若没有指定就使用默认值。
def greet1(prefix:String="Ms",name:String) = s"$prefix $name"
println(greet1(name="Brown"))
def greet2(name:String ,prefix:String="Ms") = s"$prefix $name"
println(greet2("Brown","Mr"))
可变参数
要标志一个参数匹配一个或多个输入实参,在函数定义中需要该参数类型后面增加一个星号。
def sum(items:Int*):Int= {
var total = 0
for(i <- items) total +=i
total
}
println(sum(1,2,3,4,5))
参数组
可以把参数表分解为参数组,每个参数组分别用小括号分隔。相当于链式结构
调用时的格式与定义时相对应,也使用小括号分隔开
参数组适用于后面会提到的柯里化
def max(x:Int)(y:Int) = if(x>y) x else y
println(max(10)(20))
使用表达式块调用函数
通过使用表达式块调用函数,可以完成一些计算、验证和其他动作,然后利用这个块的返回值调用函数。
举例:
def formatEuro(amt:Double) = f"$amt%.2f"
println(formatEuro(3.4646))
println(formatEuro{val rate=1.32;0.235+0.7123+rate*5.32})
递归函数
递归函数就是调用自身的函数,可能要检查某类参数或外部条件来避免函数调用陷入无限循环。
举例:求x的n次方
def power(x:Int,n:Int):Long = {
if (n>=1) x*power(x,n-1)
else 1
}
尾递归
Scala编译器可以用尾递归优化一些递归函数,使得递归调用不使用额外的栈空间,使用当前的栈空间。
必须最后一个语句是递归调用的函数才能完成尾递归优化,而且需要在函数上方加上指定的注解
上面求x的n次方的例子需要修改,将递归语句放到最后一句,如下:
@annotation.tailrec
def power(x:Int,n:Int):Long = {
if(n<1) 1
else x*power(x,n-1)
}
但是这样还不行,因为最后一个执行的语句是x和一个返回值的乘法,递归函数没在末尾,还需要修改。
@annotation.tailrec
def power(x:Int,n:Int,t:Int=1):Long={
if(n<1)t
else power(x,n-1,x*t)
}
嵌套函数
有些情况下,需要在一个方法中重复某个逻辑,但是把它作为一个外部方法又没有太大意义。对于这种情况,就可以在函数中定义另一个内部函数,这个内部函数只能在该函数中使用。
举例:求三个数的最大值
def outterFunc(a:Int,b:Int,c:Int) = {
def innerFunc(x:Int,y:Int) = if (x>y) x else y
innerFunc(a,innerFunc(b,c))
}
首类函数
函数式编程的一个关键是函数应当是首类的。
“首类”表示函数不仅能得到声明和调用,还可以作为一个数据类型用在这个语言的任何地方。
那么定义一个函数类型的值或变量,函数类型要怎么表示呢?
——答案就是!函数的类型是其输入类型和返回值类型的一个简单组合,由一个箭头(=>)从输入类型指向输出类型。当参数有两个及以上时必须使用括号
//定义一个函数info
def info(name:String,age:Int):String = {
s"${name}今年${age}岁"
}
//定义一个值f,是info函数类型的
//既然是一个函数类型的值,那么就可以当作函数去用
//info怎么使用,f就怎么使用
val f:(String,Int) => String = info
还有一种方法是使用通配符去定义函数类型的值或变量,但是这样代码可读性会降低
val fNew = info _
高阶函数
以函数作为参数或返回值的函数。
举例:
def safeStringOp(s:String,f:String=>String)={
if(s==null) s else f(s)
}
def reverser(s:String) = s.reverse
println(safeStringOp(null,reverser))
println(safeStringOp("ready",reverser))
匿名函数
没有名字的函数,定义匿名函数的语法很简单,箭头(=>)左边是参数列表,右边是函数体,而且当匿名函数作为参数时,匿名函数的参数类型可以省略
//正常定义的max函数
def max(x:Int,y:Int) = if(x>y) x else y
//赋至一个函数值
val maximize :(Int,Int) => Int = max
//用匿名函数定义
val maximize = (x:Int,y:Int) => if(x>y) x else y
def safeStringOp(s:String,f:String=>String)={
if(s==null) s else f(s)
}
println(safeStringOp(null,(s:String) => s.reverse)
println(safeStringOp("ready",(s:String) => s.reverse))
匿名函数中还可以使用通配符(_)来替换参数,但是通配符的使用有两个条件:
①有几个参数必须使用几个参数,且一个参数只能使用一次
②必须按顺序使用
def safeStringOp(s:String,f:String=>String)={
if(s==null) s else f(s)
}
println(safeStringOp(null,_.reverse)
println(safeStringOp("ready",_.reverse))
柯里化
调用函数时(包括常规函数和高阶函数),通常要在调用中指定函数的所有参数。如果想保留一些参数不想再次输入,除了使用默认值以外还可以怎么做呢?
举例:
def factoror(x:Int,y:Int) = y % x == 0 //定义一个函数
val f = factoror _ // 所有参数都不保留
val x = f(7,20) //调用
val multipleof3 = factoror(3,_:Int) //如果想保留一些参数,可以部分应用这个函数,使用通配符替代其中一个参数。
val x = multipleof3(78) //调用
要部分应用函数,还有一种更简洁的方法,可以使用有多个参数表的函数。应用一个参数表中的参数,另一个参数表不应用。这种技术称谓函数柯里化。
举例:
def factoror(x:Int)(y:Int) = y % x == 0
//将x固定成2,要是想固定y可以使用命名参数或者更改函数定义时的顺序
val isEven = factoror(2) _
val z = isEven(32)
传名参数和传值参数
Scala的解释器在解析函数参数时有两种方式:一种是先计算出参数表达式的值,再应用到函数内部;另一种是直接将未计算的参数表达式带入到函数内部,什么时候用到这个参数,什么时候计算表达式。前者叫做传值调用(call-by-value),后者叫做传名调用(call-by-name)。
传值调用在进入函数体之前就对参数表达式进行了计算,这避免了函数内部多次使用参数时重复计算其值,在一定程度上提高了效率。但是传名调用的一个优势在于,如果参数在函数体内部没有被使用到,那么它就不用计算参数表达式的值了。在这种情况下,传名调用的效率会高一点。
简单来说,用 => 定义传名参数,只传入了一个表达式,在调用时才会去执行,使用 函数名 调用;
用 () => 定义传值参数,传入计算后的值, 使用 函数名() 调用
var flag: Boolean = true
def useOrNotUse(x: Int, y: => Int) = { //将y定义为传名参数
flag match{
case true => x
case false => x + y
}
}
举例:结合下面代码的输出,理解传名和传值的执行顺序
def timeByValue(t: () => Long) = { //定义参数为传值函数
println("valueStart...")
println( t() ) //调用时使用()
println("valueStop...")
}
def timeByName(t: => Long) = { //定义参数为传名函数
println("nameStart")
println(t) //调用时不可以加()
println("nameStop")
}
def time() = {
println("获取时间内, 单位为 纳秒")
System.nanoTime()
}
def main(args: Array[String]): Unit = {
timeByValue({println("传值顺序");time;})
timeByName({println("传名顺序");time;})
}