scala(六) 高阶函数
介绍
高阶函数:以函数作为参数或返回值的方法(函数)称为高阶函数。我的理解是高阶函数
是一种思想
,它的作用能让我们的程序更加灵活。
思考:如果让你实现一个计算器,功能不多,只有+
,-
,*
,/
四个功能。
方式一:普通方式
定义四个方法(函数)
def main(args:Array[String]):Unit={
// 加法
def add(x:Int,y:Int)=x+y
// 减法
def minus(x:Int,y:Int)=x-y
// 乘法
def multiply(x:Int,y:Int)=x-y
// 除法
def division(x:Int,y:Int)=x/y
}
编写一个计算器
方法,负责统一调用
def calculator(x:Int,y:Int,options:String):Int={
if(options=="+"){
add(x,y)
}else if(options=="-"){
minus(x,y)
}else if(options=="*"){
multiply(x,y)
}else if(options=="/"){
division(x,y)
}else{
println("不支持该功能")
-1
}
}
这种方式简单,但是限制太多了,要是在加一个功能,如取余
的方法(函数),那么就需要重新定义一个取余
的方法(函数)。要是再来10
个20
个功能呢?难道需要定义这么多方法(函数)?
方式二:高阶函数一
当你看到高阶函数
的时候,应该对函数有一定的了解。先不说scala
是一个完全面向对象
的语言,就函数而言,它本身就是一个对象。FuncationN
对象,N
表示0-22 个数字(不太了解,可能会有点绕)。
既然函数是个对象,那么肯定可以作为参数用来传递。
于是我们可以定义一个计算器
函数: 除了传递正常的数值外,还需要把你具体的实现传递给我。
/**
* 计算器
* @param x 参数 1
* @param y 参数 2
* @param func 函数对象
* @return
*/
def calculator(x:Int,y:Int,func:(Int,Int)=>Int):Int={
// 具体的功能有函数来实现
func(x,y)
}
完整代码
def main(args:Array[String]):Unit={
// 加法
def add(x:Int,y:Int)=x+y
// 减法
def minus(x:Int,y:Int)=x-y
// 乘法
def multiply(x:Int,y:Int)=x-y
// 除法
def division(x:Int,y:Int)=x/y
/**
* 计算器
* @param x 参数 1
* @param y 参数 2
* @param func 函数对象
* @return
*/
def calculator(x:Int,y:Int,func:(Int,Int)=>Int):Int={
// 具体的功能有函数来实现
func(x,y)
}
// 除法
val result=calculator(12,4,division) // 3
println(result)
}
比如若现在需要使用加法;我们只需要更改 func
为 add
即可。
val result=calculator(12,4,add)
println(result) // 16
这种方式:更加灵活,思想
更加活跃,最开始接触高阶函数
的时候也是一脸懵逼,依样画葫芦的写,达不到活学活用,觉得特难。至于什么时候想明白的我也不知道,接触多了就自然而然就明白了。
回到正题,你是否还有疑问?即便是将函数以参数的形式执行,不是还得先把函数定义好吗?10个20个功能还不是得继续加代码,写函数。
哈哈确实是,但是这种思想,我们应该要明白,否则看到别人的写的函数以这样的形式都不知道啥意思就奇怪了。
对高阶函数有了一定了解后,就来玩玩scala
中的高阶函数
;看看他你能玩出什么花来。
还是以这个为例
// 加法
def add(x:Int,y:Int)=x+y
// 减法
def minus(x:Int,y:Int)=x-y
// 乘法
def multiply(x:Int,y:Int)=x-y
// 除法
def division(x:Int,y:Int)=x/y
你觉得 写这种(如上)太麻烦了,就优化一下;上面是以方法
的形式,带有def
关键字。这里改成函数
的形式。看起来更加像一个参数,有木有。
函数和方法其实是一样的
,只不过表达形式不同,没必要太纠结。
def main(args:Array[String]):Unit={
// 加法
val add=(x:Int,y:Int)=>x+y
// 减法
val minus=(x:Int,y:Int)=>x-y
// 乘法
val multiply=(x:Int,y:Int)=>x*y
// 除法
val division=(x:Int,y:Int)=>x/y
/**
* 计算器
* @param x 参数 1
* @param y 参数 2
* @param func 函数对象
* @return
*/
def calculator(x:Int,y:Int,func:(Int,Int)=>Int):Int={
// 具体的功能有函数来实现
func(x,y)
}
val result=calculator(12,4,multiply)
println(result) // 48
}
觉得没啥感觉?那我就删了,
def main(args:Array[String]):Unit={
/**
* 计算器
* @param x 参数 1
* @param y 参数 2
* @param func 函数对象
* @return
*/
def calculator(x:Int,y:Int,func:(Int,Int)=>Int):Int={
// 具体的功能有函数来实现
func(x,y)
}
val result=calculator(12,4,_+_)
println(result) //16
}
换成除法:
val result=calculator(12,4,_/_)
println(result) //3
这种方式,够离谱了吧,改到最后都不认识了。
sacla 中有很多地方都会用到下划线_
;其用途很强大的,就说说这里的_
的作用。在这里,它可以作为一个参数,拿_/_
为例,第一个_
就代表参数x的值,第二个_
就代表参数y的值。
高阶函数简化(调用时简化)
- 标准写入(以上面的案例说明)
// 定义函数
val add=(x:Int,y:Int)=>x+y
// 调用
val result=calculator(12,4,add)
// 输出结果
println(result) // 16
- 直接传递函数值
// 调用
val result=calculator(12,4,(x:Int,y:Int)=>x+y)
// 输出结果
println(result)
- 参数类型一致,类型可以省略
(x:Int,y:Int)=>x+y 与 func:(Int,Int) 是一致的。
// 调用
val result=calculator(12,4,(x,y)=>x+y)
// 输出结果
println(result)
- 如果函数的参数在函数体中,只使用过一次,可以使用
_
代替。
// 调用
val result=calculator(12,4,_+_)
// 输出结果
println(result)
- 如果 函数参数只有一个,小括号可以省略
def sayHello(str:String,func:(String)=>String): String ={
str
}
println(sayHello("hello",s=>s)) // hello
'_'的限制场景
当然使用 _
是有限制的,有些场景不可使用。
- 如果函数参数的使用顺序与参数定义的顺序不一样,此时不可用使用下划线代替。
如下: 这种情况就就不能使用下划线代替,会再次数据错误。
val result=calculator(12,4,(x,y)=>y-x)
- 如果函数体中有
()
,函数的参数在函数中小括号中以表达式
形式存在,此时不能用小括号代替。
val result=calculator(12,4,(x,y)=>(x+1)*y)
- 如果函数只有一个参数,并且在函数体中对参数没有做任何操作就直接返回的时候,不能使用
_
代替。
def sayHello(str:String,func:(String)=>String): String ={
str
}
// 普通方式
println("普通方式:",sayHello("hello",(s)=>s))
// 下划线的方式:
println("下划线的方式:",sayHello("hello",_))
输出:
(普通方式:,hello)
(下划线的方式:,Demo02$$$Lambda$7/932607259@67b64c45)
思考:为什么输出的是 Demo02$$$Lambda$7/932607259@67b64c45
?当如果是第三种情况是,_
将不再是hello
;而是 该函数的引用
;
谁的引用
?答案就是 sayHello
的引用,也就是当前函数的引用。
案例实战
- **对数组中每个元素按照指定规则进行操作,操作之后返回结果结果
数据: Array[String]("hello","spark","hadoop","flink")
规则 [可变]: 通过该规则可以获取到 字符长度
、首写字母
、转换大小写
获取每个元素的长度
案例一:获取元素长度
def main(args: Array[String]): Unit = {
val arr=Array[String]("hello","spark","hadoop","flink")
println(map(arr).toList)
}
/**
* 获取元素长度
* @param array
*/
def map(array: Array[String]): Array[AnyVal] ={
for (e<- array)yield e.length
}
List(5, 5, 6, 5)
该规则是可变的,上面的案例是获取长度,要是获取其他信息又怎么获取呢?学习完高阶函数
之后,我们自然可以想到将一个函数作为参数,将规则定义到函数中,至于什么规则,不用操心,传进来是什么就是什么就可以了。
代码优化:优化之后,将规则作为参数传进来。
def main(args: Array[String]): Unit = {
val arr=Array[String]("hello","spark","hadoop","flink")
println(map(arr,(s)=>{s.length}).toList)
}
def map(array: Array[String],func:String=>Any): Array[Any] ={
for (e<- array)yield func(e)
}
List(5, 5, 6, 5)
咦?感觉没什么?这次需求,获取列表中的首字母
def main(args: Array[String]): Unit = {
val arr=Array[String]("hello","spark","hadoop","flink")
println(map(arr,(s)=>{s.charAt(0)}).toList)
}
def map(array: Array[String],func:String=>Any): Array[Any] ={
for (e<- array)yield func(e)
}
List(h, s, h, f)
也可以这样,将字符串全部大写
def main(args: Array[String]): Unit = {
val arr=Array[String]("hello","spark","hadoop","flink")
println(map(arr,(s)=>{s.toUpperCase()}).toList)
}
def map(array: Array[String],func:String=>Any): Array[Any] ={
for (e<- array)yield func(e)
}
List(HELLO, SPARK, HADOOP, FLINK)
知道为什么需要返回 Any
吗?其主要原因是不知道规则是什么。比如:第一次是长度 类型肯定是Int
,第一次是首字母肯定的是Char
,第三次是转转大写,是一个String
。就是因为不确定,所以需要指定为Any
。
接下来就是代码优化:
- 由于参数只有一个,所以可以不用带
()
println(map(arr,s=>{s.toUpperCase()}).toList)
- 块中只有语句代码所以可以不用指定
{}
println(map(arr,s=>s.toUpperCase()).toList)
- 当然方法没参数,也可以不用带
()
println(map(arr,s=>s.toUpperCase).toList)
- 参数只有一个且没有重复使用的地方,所以可以使用
_
代替
println(map(arr,_.toUpperCase).toList)
运行结果:
List(HELLO, SPARK, HADOOP, FLINK)
-
对数组中的数据按照指定规则过滤
数据: Array[Int](1,4,2,7,9,10)
规则: [可变] 通过此规则,可以获取,奇偶数
,3的倍数
等。
案例一:获取所有的奇数
def main(args: Array[String]): Unit = {
val arr=Array[Int](1,4,2,7,9,10)
println(filter(arr).toList)
}
/**
* 过滤
* @param arr
* @return
*/
def filter(arr:Array[Int]):Array[Int]={
for (i <- arr if i % 2 != 0) yield i
}
List(1, 7, 9)
规则是可以的,通过观察,发现 if i % 2 != 0
就是我们想要的规则;于是使用高阶函数进行改进。
需要键元素传给函数,然后在函数中判断是否是需要的元素;所以传入的参数是Int
返回的是Boolean
。
func:Int=>Boolean
def main(args: Array[String]): Unit = {
val arr=Array[Int](1,4,2,7,9,10)
println(filter(arr,(i)=>{i%2!=0}).toList)
}
/**
* 过滤
* @param arr
* @return
*/
def filter(arr:Array[Int],func:Int=>Boolean):Array[Int]={
for (i <- arr if func(i)) yield i
}
List(1, 7, 9)
案例二:获取集合中所有的偶数
def main(args: Array[String]): Unit = {
val arr=Array[Int](1,4,2,7,9,10)
println(filter(arr,i=>i%2==0).toList)
}
/**
* 过滤
* @param arr
* @return
*/
def filter(arr:Array[Int],func:Int=>Boolean):Array[Int]={
for (i <- arr if func(i)) yield i
}
List(4, 2, 10)
案例三:获取结合中是3的倍数
def main(args: Array[String]): Unit = {
val arr=Array[Int](1,4,2,7,9,10)
println(filter(arr,_%3==0).toList)
}
/**
* 过滤
* @param arr
* @return
*/
def filter(arr:Array[Int],func:Int=>Boolean):Array[Int]={
for (i <- arr if func(i)) yield i
}
List(9)
-
对数组中元素按照指定规则分组
数据: Array[String]("zhangsan man beijing","lisi woman shenzhen","zhaoliu man shenzhen")
规则: 按照规则进行分组
通过上面两个案例的套路
;应该知道该如何编写程序,按照规则进行分组,比如按照性别
,地区
等。
案例一:根据性别
分组
def main(args: Array[String]): Unit = {
val arr=Array[String]("zhangsan man beijing","lisi woman shenzhen","zhaoliu man shenzhen")
//根据 性别分组
println(group(arr,s=>{s.split(" ")(1)}))
}
/**
* 分组
* @param arr
* @param func
* @return
*/
def group(arr:Array[String],func:String=>String):util.HashMap[String,util.ArrayList[String]]={
// 用于存放 分组数据
val map=new util.HashMap[String,util.ArrayList[String]]()
// 分组;具体按照什么来分组,有func 指定。
// 只需要将 每个元素传给 func 即可。
var list:util.ArrayList[String]=null
for(e <-arr ;key=func(e)){
// 校验key是否存在,
if(map.containsKey(key)){
list=map.get(key)
}else{
// 若key不存在,需要创建 集合
list=new util.ArrayList[String]
}
list.add(e)
// 装回map中
map.put(key,list);
}
map
}
{woman=[lisi woman shenzhen], man=[zhangsan man beijing, zhaoliu man shenzhen]}
案例一:根据地区
分组
def main(args: Array[String]): Unit = {
val arr=Array[String]("zhangsan man beijing","lisi woman shenzhen","zhaoliu man shenzhen")
//根据 地区分组
println(group(arr,s=>{s.split(" ")(2)}))
}
/**
* 分组
* @param arr
* @param func
* @return
*/
def group(arr:Array[String],func:String=>String):util.HashMap[String,util.ArrayList[String]]={
// 用于存放 分组数据
val map=new util.HashMap[String,util.ArrayList[String]]()
// 分组;具体按照什么来分组,有func 指定。
// 只需要将 每个元素传给 func 即可。
var list:util.ArrayList[String]=null
for(e <-arr ;key=func(e)){
// 校验key是否存在,
if(map.containsKey(key)){
list=map.get(key)
}else{
// 若key不存在,需要创建 集合
list=new util.ArrayList[String]
}
list.add(e)
// 装回map中
map.put(key,list);
}
map
}
{shenzhen=[lisi woman shenzhen, zhaoliu man shenzhen], beijing=[zhangsan man beijing]}
代码简写:
//根据 性别分组
println(group(arr,_.split(" ")(2)))
-
对数组中的所有元素按照指定规则聚合
数据: Array[Double](1,4,2,7,9,10)
规则: 求总和,求乘积,求差等。
案例一:求出集合中的总和
def main(args: Array[String]): Unit = {
val arr=Array[Double](1,4,2,7,9,10)
println(reduce(arr,(x,y)=>{x+y}))
}
def reduce(array: Array[Double],func:(Double,Double)=>Double): Double ={
var tmp=array(0)
for(i <- 1 until array.length){
tmp=func(tmp,array(i))
}
tmp
}
33.0
案例二:求出集合中的积
def main(args: Array[String]): Unit = {
val arr=Array[Double](1,4,2,7,9,10)
println(reduce(arr,(x,y)=>x*y))
}
def reduce(array: Array[Double],func:(Double,Double)=>Double): Double ={
var tmp=array(0)
for(i <- 1 until array.length){
tmp=func(tmp,array(i))
}
tmp
}
5040.0
案例二:求出集合中的差
def main(args: Array[String]): Unit = {
val arr=Array[Double](1,4,2,7,9,10)
println(reduce(arr,_-_))
}
def reduce(array: Array[Double],func:(Double,Double)=>Double): Double ={
var tmp=array(0)
for(i <- 1 until array.length){
tmp=func(tmp,array(i))
}
tmp
}
-31.0
- 根据指定规则获取数组中的最大值
数据: Array[String]("zhangsan 20 3000","lisi 18 4500","zhaoliu 33 3500")
规则: 根据不同的规则,求出最大值,如按照,年纪,薪资等。
案例一:按照获取年龄最大的用户
def main(args: Array[String]): Unit = {
val arr=Array[String]("zhangsan 20 3000","lisi 18 4500","zhaoliu 33 3500")
// 获取年龄最大的用户信息
println(max(arr,s=>s.split(" ")(1).toInt))
}
/**
* 获取最大值
* @param array
* @param func
* @return
*/
def max(array: Array[String],func:String=>Int):String={
// 用于记录最大值元素的下标。
var index=0
for(i <- 1 until array.length){
// 进行比较,记录最大值的元素下标
if(func(array(index)) < func(array(i))){
index=i
}
}
array(index)
}
案例二:获取薪资最高的用户信息
def main(args: Array[String]): Unit = {
val arr=Array[String]("zhangsan 20 3000","lisi 18 4500","zhaoliu 33 3500")
// 获取年龄最大的用户信息
println(max(arr,_.split(" ")(2).toInt))
}
/**
* 获取最大值
* @param array
* @param func
* @return
*/
def max(array: Array[String],func:String=>Int):String={
// 用于记录最大值元素的下标。
var index=0
for(i <- 1 until array.length){
// 进行比较,记录最大值的元素下标
if(func(array(index)) < func(array(i))){
index=i
}
}
array(index)
}
lisi 18 4500
代码优化:
def max(array: Array[String],func:String=>Int):String={
// 用于记录最大值元素的下标。
var index=0
for(i <- 1 until array.length;tmp=func(array(i)) if func(array(index))<tmp) index=i
array(index)
}
之后的很多都没做说明,累了,脑袋优点晕,以后再说吧。