目录
函数基础
基本语法
函数参数
函数至简原则
至简原则细节
函数高级
高阶函数
函数的3种高阶用法:
1、函数作为值传递
2、函数作为参数传递
匿名函数作参数传递
3、函数作为返回值传递
匿名函数的至简规则
高阶函数的应用
案例-将数组中的元素全部+1后返回
匿名函数
练习1-匿名函数作为值传递:
练习2-函数作为返回值
函数柯里化&闭包
案例2的闭包实现:
案例2的柯里化实现:
闭包案例 - 常量a+变量b
案例 - 柯里化
递归
斐波那契数列
阶乘
尾递归
Scala实现尾递归求阶乘
Java实现尾递归求阶乘
控制抽象
传值调用
传名调用
应用案例 - 自定义函数实现while
惰性加载
函数基础
Scala 的函数式编程以及面向对象的特点,使它能够很好的应用到大数据场景下,比如 Spark、Kafka 的底层都是 Scala 编写的。
基本语法
在Java中,方法只能够在类下面声明,不可以在main方法以及其它方法内部声明,但是Scala可以,因为Scala是一个函数式编程,函数是一等公民。
所以,我们通常把main方法内部定义的方法叫做函数,main外部声明的方法才叫做对象的方法。
object Test{
def main(args: Array[String]): Unit = {
//定义函数
def sayHello(name: String): Unit = {
println("hello " + name)
}
//调用函数
sayHello("GGBond")
//调用对象的方法
Test01_Function.sayHello("Tom")
}
//定义对象的方法
def sayHello(name: String): Unit = {
println("hello " + name)
}
}
可以看到,我们可以直接通过函数名调用函数,但是在伴生类中,一切类变量和方法都是静态的,所以我们需要通过类名才可以调用方法。
函数参数
- 可变参数
- 如果参数列表中存在多个参数,那么可变参数一般放置在最后
- 参数默认值,一般将有默认值的参数放置在参数列表的后面
- 带名参数
def main(args: Array[String]): Unit = {
//1.可变参数
def f1(names: String*): Unit = {
println(names)
}
f1("alice","tom","GG bond")
//2.如果参数列表中存在多个参数,那么可变参数一般放置在最后
def f2(grade: String, names: String*): Unit = {
println(grade)
println(names)
}
f2("大二","alice","tom","GG bond")
//3.参数默认值,一般将有默认值的参数放置在参数列表的后面
def f3(name: String = "GG bond"): Unit = {
println(name)
}
f3()
f3("燕双鹰")
//4.带名参数
def f4(name: String, age: Int): Unit = {
println(s"${name}的年龄=${age}")
}
f4("GG bond",14)
f4(age = 13,name = "熊大")
}
函数至简原则
代码能省则省
至简原则细节
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
- 如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
object Test{
def main(args: Array[String]): Unit = {
//(1)return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
def f0(name: String): String = {
return name
}
println(f0("GG bond"))
def f1(name: String): String = {
name
}
println(f1("GG bond"))
//(2)如果函数体只有一行代码,可以省略花括号
def sum(a: Int,b: Int): Int = a+b
//(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
def add(a: Int,b: Int) = a+b //因为参数都是Int类型,所以可以自己推断返回值类型
//(4)如果有 return,则不能省略返回值类型,必须指定
def f3(name: String): String = {
return name
}
//(6)Scala 如果期望是无返回值类型,可以省略等号
def f4(name: String){
println("hello")
}
//(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def f5() = "name"
println(f5)
//(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f6 = "hi"
//(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def say_Hi(name: String): Unit = {
println("hi" + name)
}
//匿名函数 匿名函数的返回值是一个函数
val say_Hello = (name: String) => {"hello " + name}
//调用匿名函数
println(say_Hello("GG bond"))
}
}
函数高级
高阶函数
高阶函数指的是接受一个或多个函数作为参数,并返回一个函数作为结果的函数。函数是Scala中的一等公民,地位很高,这体现在它可以在main方法中直接定义并通过函数名来直接使用,而不用向Java一样,需要再对象下面声明方法,来通过对象或者类名来调用。高阶函数可以减少代码的重复,并增加代码的灵活性和可重用性。
通常我们的业务数据在高阶函数中写死,等待传进来的函数参数来作处理。
函数的3种高阶用法:
- 函数作为值传递
- 函数作为参数传递
- 函数作为返回值
1、函数作为值传递
函数作为返回值赋值给一个变量,这个变量类型是一个函数对象。如果直接输出的话,如果这个函数没有参数,将直接输出这个函数的返回值,否则直接输出这个对象的地址(对象引用)。我们也可以通过返回值来直接使用,因为返回值本身就是一个函数对象,我们可以直接+(参数)来调用函数。
def main(args: Array[String]): Unit = {
//定义一个普通函数 输入num返回num+1
def f(num: Int): Int = {
num + 1
}
//1.函数作为值传递 -两种方式返回函数对象
val f1: Int => Int = f
val f2 = f _
//我们可以直接输出函数对象,也可以直接利用函数对象进行使用传递参数
println(f1) //输出这个对象的地址(也叫对象引用)
println(f1(9))
println(f2) //输出这个对象的地址(也叫对象引用)
println(f2(10))
//定义一个函数无参,直接返回1
def fun(): Int = {
1
}
val f3 = fun //因为fun是无参的,所以这里返回的是返回值 1
val f4 = fun _ //返回函数体
val f5: () => Int = fun
println(f3) //输出 1
println(f4) //输出这个对象的地址(也叫对象引用)
println(f5) //输出这个对象的地址(也叫对象引用)
}
2、函数作为参数传递
我们可以定义两个函数,分别实现两个数的加和减,然后我们可以将这两个函数作为参数传递给另一个函数,来实现对两个数的处理(数据是写死的)。
//定义一个函数 sum 求和
def sum(a: Int,b: Int): Unit = {
println(s"${a} + ${b} = ${a+b}")
}
//定义一个函数 sub 求差,函数体只有一行可以省去花括号{}
def sub(a: Int,b: Int): Unit = println(s"${a} - ${b} = ${-b}")
def fun(f: (Int,Int) => Unit):Unit = {
f(1,2)
}
fun(sum) //输出 1 + 2 = 3
fun(sub) //输出 1 - 2 = -1
前面我们数据是写死的,也就是说,我们的高阶函数只能够对 1和2进行处理,显然很没用!这里我们直接将要计算的数据页当做参数传递进来。
def doEval(f: (Int,Int) => Int,a: Int ,b: Int): Int = {
f(a,b)
}
def add(a: Int,b: Int): Int = {
a + b
}
//使用普通函数作为参数
doEval(add,1,2)
//使用匿名函数做参数
doEval((a,b) => a + b,1,2)
//简化代码
doEval(_ + _,1,2)
匿名函数作参数传递
我们可以直接将匿名函数作参数传入fun,更加简化代码。
//以函数作为参数
def fun(f: (Int,Int) => Unit): Unit = f(1,2)
//直接传入匿名函数
fun((a: Int,b: Int) => println(s"${a} + ${b} = ${a+b}"))) //输出 1 + 2 = 3
//高阶函数定义了函数参数的类型,匿名函数这里就不需要再指定函数类型了
fun((a,b) => println(s"${a} - ${b} = ${a-b}"))) //输出 1 - 2 = -1
3、函数作为返回值传递
函数作为返回值后返回函数对象,我们可以通过函数对象来使用该函数。
//3.函数作为返回值传递
def f6(): Int => Unit = {
def f7(a: Int): Unit = {
println("f7被调用 " + a)
}
f7 //返回函数对象
}
//获取f7函数对象 并使用
val res = f6()
//输出:
// f7被调用 10
// ()
// 因为f7的返回值为 Unit类型
println(res(10))
匿名函数的至简规则
- 参数的类型可以省略,会根据形参进行自动的推导
- 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参 数超过 1 的永远不能省略圆括号。
- 匿名函数如果只有一行,则大括号也可以省略
- 如果参数只出现一次,则参数省略且后面参数可以用下划线 _ 代替
//定义一个函数,以函数作参数
def say_Name(f: String => Unit): Unit = f("GG Bond")
//普通调用
say_Name((name: String) => {println(name)})
//1.参数的类型可以省略,会根据形参进行自动的推导
say_Name( (name) => {println(name)})
//2.如果参数只有一个 括号省略
say_Name( name => {println(name)} )
//3.匿名函数如果只有一行,则大括号可以省略
say_Name( name => println(name) )
//4.如果参数只出现一次,则参数省略且后面参数可以用_代替
say_Name( println(_) )
//5.如果可以推断出当前传入的println()是一个函数体,而不是调用语句,可以直接省略_
say_Name( println )
高阶函数的应用
高阶函数对集合的处理更加体现了Sala对于处理大数据的优势。
案例-将数组中的元素全部+1后返回
val arr: Array[Int] = Array(1,2,4,3,8)
//对数组进行处理,把对数组的操作进行抽象
/**
*
* @param array 待处理的数组
* @param op 操作函数(Int => Int)将数组中的数处理后变为另一个数
* @return 返回新数组
*/
def arrayOperation(array: Array[Int], op: Int => Int): Array[Int] = {
for (elem <- array) yield op(elem)
}
//定义操作函数
def addOne(elem: Int): Int = elem + 1
//调用函数
val res_arr: Array[Int] = arrayOperation(arr,addOne)
//用逗号间隔输出新数组的内容
println(res_arr.mkString(","))
//使用匿名函数实现
val res2_arr = arrayOperation(arr,elem => elem + 1)
//这里的后面的elem可以用下划线代替 _
val res2_arr = arrayOperation(arr,elem => _ + 1)
println(res2_arr.mkString(","))
匿名函数
前面已经用过了,这里在详细解释一下。
说明:没有函数名的函数就是匿名函数。
表达式:(a: Int,b: Int)=>{函数体}
注意:匿名函数的定义没有返回值类型,但是可以作为高阶函数的参数时指定。
//以函数作为参数
def fun(f: (Int,Int) => Int): Unit = f(1,2)
//直接传入匿名函数
fun((a: Int,b: Int) => a + b )) //返回 3
//高阶函数中指定了参数函数的参数类似,所以这里可以省去匿名函数的参数类型
fun((a,b) => a - b )) //返回-1
//如果每个参数只出现一次,可以这样写
fun( _ + _) //对应 a + b
fun( _ - _) //对应 a - b
//如果要实现 b-a 可以写作 -a+b
fun((a,b) => -a + b)
fun( -_ + _) //对应 -a + b
练习1-匿名函数作为值传递:
定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。
要求:调用函数 fun(0, “”, ‘0’)得到返回值为 false,其它情况均返回 true。
//匿名函数在没被高阶函数指定返回值类型的时候,需要声明参数名和类型
val fun = (a: Int,b: String,c: Char) => {
if (a == 0 && b == "" && c == '0')
false
else
true
}
println(fun(1,"",'0')) //true
练习2-函数作为返回值
定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。 它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接 收一个 Char 类型的参数,返回一个 Boolean 的值。
要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true。
//表示返回一个输入类型为String输出类型为(Char => Boolean)的函数
def func(a: Int): String => (Char => Boolean) = {
//表示返回一个输入类型为Char输出类型为Boolean的函数
def f1 (b: String): Char => Boolean = {
//表示返回一个返回值类型为Boolean的值
def f2(c: Char): Boolean = {
if (a == 0 && b == "" && c == '0') false else true
}
f2
}
f1
}
println(func(0)("")('0')) //false
使用匿名函数简写
函数柯里化&闭包
- 闭包:如果一个函数 ,访问到了它外部变量的值,那么这个函数和它所处的环境,称为闭包
- 函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
我们学习闭包和柯里化是为了更加简洁地实现调用上层函数的参数,因为在上面的案例2中,我们通过嵌套函数来实现读取上层函数的参数显然代码很复杂,所以我们可以使用柯里化来实现。
案例2的闭包实现:
//匿名函数简写
def func1(a: Int): String => (Char => Boolean) = {
//外层指定了返回值类型 内层的匿名函数就不需要指定参数类型
b => c => if (a == 0 && b == "" && c == '0') false else true
}
println(func(1)("")('0')) //tue
案例2的柯里化实现:
//柯里化
def func2(a: Int)(b: String)(c: Char): Boolean = {
if (a == 0 && b == "" && c == '0') false else true
}
//调用
println(func2(0)("")('0')) //true
我们可以看到,柯里化更加简单,其实柯里化的底层就是闭包来实现的。
闭包案例 - 常量a+变量b
这并不是一个简单的两数相加,因为实际开发中,我们可能遇到大量的变量对常量不断运算,比如大量数字分别对4相加、对5相加,这就需要定义两个函数addByFour、addByFive;但是如果常量有几百种呢,这就需要定义几百个函数,显然过于复杂。所以这就需要使用双层嵌套函数,以返回值为函数:第一层函数输入常量,第二层函数输入变量。
//A是任意常量
def addByA(a: Int): Int=>Int = {
def addB(b: Int): Int = {
a + b
}
addB
}
//不断简化代码
//既然直接返回函数 我们直接定义一个匿名函数即可 scala默认返回最后一行(一个方法可以看作一行)作为返回值
def addByA1(a: Int): Int=>Int = {
(b: Int) => a + b
}
//指定外层函数指定了返回值类型 内层就不需要写输入类型了
def addByA2(a: Int): Int=>Int = b => a + b
//内层的变量 b 只在函数体出现了一次 所以可以用 _ 替换
def addByA3(a: Int): Int=>Int = a + _
//调用
val fun = addByA3(5) //接收一个函数(任意数+5)作为返回值
println(fun(10)) //调用函数 输出15
案例 - 柯里化
在纯粹的函数式编程中其实是不存在多参数这样的定义的,只是Scala为了兼容其它语言才不做限制的。所以引入柯里化来实现这样的一个效果,将一个含多个参数的参数列表转变为多个仅含一个参数的参数列表。
柯里化的底层就是闭包。
//柯里化 底层是闭包实现分层调用
def addCurrying(a: Int)(b: Int): Int = a + b
//调用
println(addCurrying(4)(36))
递归
斐波那契数列
def fb(i: Int): Int = {
if (i ==1 || i == 2)
1
else
fb(i-1) + fb(i+2)
}
阶乘
def jieC(n: Int): Int = {
if (n==1)
1
else
jieC(n-1)*n
}
尾递归
我们知道,在做递归的时候,我们其实是方法不断调用,比如上面的阶乘案例,当我们希望得到5的阶乘时,函数的调用会不断压栈,到达阶乘的最小值1的时候原路返回。显然如果是10亿的阶乘,肯定会造成栈溢出的问题。
所以这就需要引入尾递归的概念,上面的普通递归需要不断压栈最后出栈,比如5的阶乘,它的返回值是 1 -> 2*1 -> 3*2*1 -> 4*3*2*1 -> 5*4*3*2*1
而我们的尾递归刚好相反,它的返回值是 5 -> 5*4 -> 5*4*3 -> 5*4*3*2 -> 5*4*3*2*1,这样,在调用函数时,不会创建新的栈帧,而是直接复用当前栈帧,因此占用的栈资源是固定的。
Scala实现尾递归求阶乘
def tailJieC(n: Int): Int = {
def loop(n: Int,tmp: Int): Int = {
if (n==0)
return tmp
loop(n - 1,tmp * n)
}
loop(n,1)
}
Java实现尾递归求阶乘
public static int JC(int n){
return loop(n,1);
}
public static int loop(int n,int tmp){
if (n==0){
return tmp;
}else {
return loop(n-1,tmp*n);
}
}
控制抽象
传值调用
参数类型:普通参数
println("=====传值调用======")
//1.传值参数
def f0(a: Int): Unit = {
println("a = " + a)
}
//数值做参数
f0(1)
def f1(): Int = {
println("f1被调用")
2
}
//函数返回值做参数
f0(f1())
输出结果
1
2
传名调用
参数类型:传递的不再是具体的值,而是代码块
println("=====传名调用======")
def f1(): Int = {
println("f1被调用")
2
}
//2.传名参数
//f2的参数是代码块 要求代码块的返回值是Int
def f2(a: => Int): Unit = {
println("a = " + a)
println("a = " + a) //a被调用几次 代码块就被执行几次
}
f2(23) //输出两个23
f2(f1()) //执行两次f1() 相当于把f2的函数体中的 a 都替换做 f1() 这也说明传名调用传递的是代码块而不是返回值
f2({
println("这是一个代码块")
20
})
输出结果
=====传名调用======
a = 23
a = 23
f1被调用
a = 2
f1被调用
a = 2
这是一个代码块
a = 20
这是一个代码块
a = 20
应用案例 - 自定义函数实现while
//1.常规的while循环
var n = 10
while (n > 0){
println(n)
n -= 1
}
//2.自定义函数实现while
// 用闭包实现一个函数,将代码块作为参数传入,递归调用
def myWhile(condition: =>Boolean): (=>Unit)=>Unit = {
//内层实现递归 参数就是循环体-代码块
def doLoop(op: => Unit): Unit= {
if (condition){
op
myWhile(condition)(op)
}
}
doLoop _
}
//给n重新赋值
n = 10
myWhile(n >= 1)({
println(n)
n -=1
})
简化代码
//3.用匿名函数简化代码
def myWhile2(condition: =>Boolean): (=>Unit)=>Unit = {
//内层实现递归 参数就是循环体-代码块
op => {
if (condition){
op
myWhile2(condition)(op)
}
}
}
//4.用柯里化简化代码
def myWhile3(condition: => Boolean)(op: =>Unit): Unit = {
if (condition){
op
myWhile2(condition)(op)
}
}
惰性加载
懒加载(Lazy Loading)是一种常见的优化策略,它可以延迟对象的初始化时间,减少应用程序的启动时间和内存消耗。在懒加载的策略下,只有在需要访问具体的对象时才会进行初始化。在Scala中,我们使用关键字 'lazy' 来实现懒加载。
lazy val res: Int = compute()
def compute(): Int = {
// 进行复杂的计算
Thread.sleep(1000)
//返回计算结果
100
}