一、Scala函数式编程概述
1、Scala的函数式特性
Scala作为一门函数式编程和面向对象结合的语言,使用其函数式编程的风格,可以使代码更加简洁有弹性,同时Scala推崇变量都是不可变的、用val定义的变量类似于java中的final关键字。 函数与不可变特性更加契合现在的大数据处理与并行计算,在利用多机器、多核心计算的时候,Scala中函数式编程没有可变变量Var,那么就不会有内存共享的问题,也不会产生负作用函数。
2、函数式编程的好处
1)主要是函数的不变性带来的,没有可改变的状态,函数的引用就是透明的和没有副作用的存在。 函数式编程中不变形使得线程之间不共享状态,避免了死锁的问题,多处理器并发的情况下更好的提高程序处理效率。
2)函数不依赖外部的状态也不修改外部的状态,对于函数的调用,更加不会依赖于调用的时间和函数具体所在的位置,是的代码更容易进行推理、调试、单元测试。
二、Scala函数的定义
1、函数定义基本格式
def 函数名 (参数A:A类型,参数B:B类型,... ):返回值类型 = { 函数执行体 }
2、函数定义示例
//常规函数定义:计算x+y的函数
def add(x:Int,y:Int):Int={
x+y
}
//常规函数定义:定义返回类型为空的函数
def printHello():Unit={
println("Hello World")
}
//省略返回值,让程序根据代码块,根据最后一个表达式的类型推导出返回值类型
def doSometing(x:Int){x+"something"}
//返回值为Unit的时候,可以省略返回值类型与“=”符号
def printHello(){println("HelloWorld")}
//当函数体只有一条语句,可以省略花括号
def max(x:Int,y:Int)=if (x>y) x else y
三、Scala函数的特性
1、递归
Scala的递归实现中,没有使用循环,没有使用可变状态,并且函数体代码更加的简洁有条理性,也不需要在累积器中保存每次运行产生的结果,使用函数来保存中间结果。
//n阶乘的实现
def funFactorial(n:Int):Int={
if(n==0) return 1
else n*funFactorial(n-1)
}
2、函数字面量和占位符
函数字面量可以作为值进行传递,当然也可以定义或者调用,还可以把它们写成匿名的方式,称之为函数字面量
//函数字面量,函数作为对象进行使用、传值
def testFristClassValue(): Unit ={
//函数字面量((A:Long)=>A+1)保存到变量(a)中
val a=(A:Long)=>A+1
//函数字面量,保存到变量 b 中
val b=(A:Int)=>{
println("hello scala")
println("hello spark")
A-10
}
//调用对象,重新赋值
println(a(10))
println(b(20))
}
输出结果:
11
hello scala
hello spark
10
Scala还有一处非常强大的地方是类型推导,编译器可以推断出函数字面量的参数类型,所以为了代码的简洁性,通常会省略类型以及参数的括号;也可以使用通配符 “_” 做单个参数的占位符。
//测试占位符、类型推导、通配符
def testPlaceholder(): Unit ={
//一元函数
println( List(1,2,3,4,5,6).map((x:Int)=>x+2))
println( List(1,2,3,4,5,6).map(x=>x+2))
//占位符 表示一个或者多个参数,但是需要满足每个参数只能出现一次
//并且多个占位符时,需要和参数依次对应起来
println( List(1,2,3,4,5,6).map(_+2))
val array=Array(1,2,3,4,5,6,7,8)
//根据类型推导,省略了item的类型,然后循环打印出大于2的元素
val out=array.filter(item=>item>2).foreach(println(_))
//进一步使用通配符
val out2=array.map(_+10).filter(_>12).foreach(println(_))
}
输出结果:
List(3, 4, 5, 6, 7, 8)
List(3, 4, 5, 6, 7, 8)
List(3, 4, 5, 6, 7, 8)
3
4
5
6
7
8
13
14
15
16
17
18
4、部分应用函数
部分应用函数也成为偏应用函数,只提供了或者指定了部分参数的函数就是部分应用功能函数,它是一种表达式,在Scala中档进行函数调用时不需要指定所有的参数,只需要传入部分参数即可。
//偏应用函数,可以创建一个函数的部分实现,作为一个新的函数,再调用新的函数
def testPartiallyAppliedFunction(): Unit ={
//定义函数
def functionOps=(_:Int)+(_:String)
//完全调用
println(functionOps(1,"Spark"))
//部分应用,基于部分参数生成新的函数对象
val part=functionOps(2,_:String)
//调用新的函数对象
println(part("scala Spark"))
}
输出结果:
1:Spark
2:scala Spark
5、闭包
当函数超出作用范围或者函数执行完成后,其内部的变量依旧可以被外界访问,就称该函数为闭包,也就是说依照函数字面量中运行期创建的函数值(对象)被称为闭包。
//测试闭包
def testClosure(): Unit={
//调用函数字面量,赋值给对象hello
val hello=hiScala("yiming")
//再调用hello对象,传入参数Lod,hello又转过来访问hiScala函数的内容
hello("Hi")
}
//定义函数,接收Lod参数,打印
def hiScala(humen:String)=(Lod:String)=>println(Lod+":"+humen)
输出结果:
Hi:yiming
6、Curring(柯里化)
Curring(柯里化)是把接受多个参数的函数转换成为接受一个单一参数(也可以为多参数)的函数。Curring主要表现在接受到的是一个参数列表
//Curring(柯里化) 函数测试
def testCurring(): Unit={
//普通函数调用
notCurring("not","curring")
//curring函数调用,使用一个参数列表
curring("is")("curring")
}
//普通函数
def notCurring(x:String,y:String):Unit={
println(x+"_"+y)
}
//Curring函数,改函数定义的是一个参数列表
def curring(x:String)(y:String):Unit={
println(x+"_"+y)
}
输出结果:
not_curring
is_curring
7、偏函数
偏函数不同于偏应用函数,偏函数是一个数学概念,把只对函数定义域的一个子集进行定义的函数成为偏函数。
//偏函数测试
def testPartialFunction(): Unit ={
//调用people函数,只关心child和man的域
println(people(3))
println(people(45))
//给函数增加其它的定义域,生成新的函数
val oldMan:(Int=>String)=people orElse{case age if age>60 =>"oldMan"}
println(oldMan(80))
}
//people函数,只关心child和man的域,使用PartialFunction定义,即可只给出一部分函数的定义域
val people:PartialFunction[Int,String]={
case age if age<=20=>"Child"
case age if age<=60 && age>20 =>"Man"
}
输出结果:
Child
Man
oldMan
8、高阶函数
1)高阶函数是指函数可以作为参数、函数也可以作为返回值,这是scala最具魅力的特性之一。
//高阶函数测试-函数作为参数使用
def testHigherOrderFunction(): Unit ={
//定义一个普通函数
val printName=(name:String)=>println(name)
//讲普通函数作为参数传入高阶函数
higherPrintName(printName,"My name is *** ,Spark is Wonderful!")
}
//高阶函数定义,第一个参数为函数,满足该签名的函数都可以作为参数传入
def higherPrintName(myFunction:(String)=>Unit,context:String){myFunction(context)}
输出结果:
My name is *** ,Spark is Wonderful!
2)可以将函数赋值与引用,将函数创建为特殊类型的实例
//高阶函数测试-将函数赋值与引用
def funcValue(): Unit ={
//调用一个函数,传入一个Int类型与一个匿名函数,并且将函数赋值与引用(result)
val result=resultValue(10,x=>if(x>0) x+3 else 0)
//打印result的结果
println(result)
}
//定义一个高阶函数,
def resultValue(num:Int,total:Int=>Int):Int={
var hello=0
for(i <- 1 to num){
hello+=total(i)
}
hello
}
运行结果
85
3)函数的参数列表可以有若干个,但是要申明函数参数的类型。对于重复的参数列表,使用*号来标注
//高阶函数测试-重复参数的使用
def paramTest(): Unit ={
//创建一个函数,接收可变的参数列表,然后循环打印
def functionOps(args:String*)=for(arg <- args)print(arg)
//传入多个参数
functionOps("this ","is ","parmater ","test!")
//使用一个数组
val paramater=Array(" this ","is ","array ","parmater!")
//将数组中的每一个元素当做一个参数进行传入,需要使用“_*”来标注
functionOps(paramater:_*)
}
运行结果
this is parmater test! this is array parmater
4)函数作为参数进行传递时,可以使用占位符代替参数,简化代码
//高阶函数测试-使用占位符代替参数
def placeholderTest(): Unit ={
//定义一个函数,参数分别为一个函数与一个字符串
def DT(myFunction:(String)=>Unit,data:String){myFunction(data)}
//函数调用
DT((name:String)=>println(name),"General use")
//省略类型String
DT(name=>println(name),"no type")
//省略参数name
DT(println(_),"laceholder")
//能省略的都省略掉
DT(println,"no param")
}
运行结果
General use
no type
laceholder
no param
5)多参数函数值,在scala中函数值时真正的对象,而且函数值本身可以有多个参数,参见下列代码中的mulfun:(Int,Int)=>Int
//高阶函数测试-多参数函数值
def mulFuncTest(): Unit ={
val arr=Array(23,21,54,664,33,2)
val sum=mulFuncOps((temp,x)=>temp+x,arr,3)
println(sum)
}
//定义高阶函数,其中第一个作为参数的函数,本身有拥有多个参数
def mulFuncOps(mulfun:(Int,Int)=>Int,array:Array[Int],item:Int): Int ={
var tmp=item
array.foreach{
x=>tmp=mulfun(tmp,x)
}
tmp
}
运行结果
800
6)重用函数值,使用重用函数值主要用于代码的重用,在scala中使用函数值可以创建很好地重用性代码来消除代码冗余和代码重复
//高阶函数测试-重用函数值
def reuseFuncTest(): Unit ={
val arr=Array(1,2,3,4,5)
//定义重用函数值
val reuseFunc=(tmp:Int)=>println("result is:"+tmp)
//函数重用
addFunc(reuseFunc,arr)
multiplyFunc(reuseFunc,arr)
}
//定义累加函数
def addFunc(dofunc:(Int)=>Unit,array:Array[Int]): Unit ={
var tmp=1
array.foreach{ x=>tmp+=x}
dofunc(tmp)
}
//定义累乘函数
def multiplyFunc(dofunc:(Int)=>Unit,array:Array[Int]): Unit ={
var tmp=1
array.foreach{ x=>tmp=tmp*x}
dofunc(tmp)
}
运行结果:
result is:16
result is:120