函数式编程
- 稀里糊涂的学往往就成了死记硬背,可以参考一下知乎的这篇文章大致理解一下什么是函数式编程,它的优势在哪里,以及一些主要的特性
- 我们可以将这里的函数理解为表达式,更侧重数学的推演过程,所以我们会看到scala中if/else会有返回值,需要一个var/val接收
- 为了方便查看每一行代码的运行结果,推荐使用scala的REPL交互式解释器,可以即时编译、运行代码并返回结果,方便前期学习和测试
-
REPL:R(read)、E(evaluate) 、P(print)、L(loop),使用方法:
如果是方法体回车即可换行,使用ctrl+d
结束输入
:quit
退出交互式解释器
为了方便展示后面的代码块还是使用IDEA方式 - 使用Spark/Flink框架的大量业务代码都会使用到函数式编程
- 主要是结合List的一些操作
遍历 - foreach
- 主要作用是对集合元素遍历查看
方法描述:foreach(f: (A) ⇒ Unit): Unit
接收一个函数对象,函数为匿名函数,输入参数为集合的元素
val list = ListBuffer(1,2,3,4)
list.foreach((x:Int)=>println(x))
list.foreach(x=>println(x)) // 参数类型可省略
list.foreach(println(_)) // 当函数参数,只在函数体中出现一次且没有嵌套调用
映射 - Map
- 集合的映射操作是将来在编写Spark/Flink用得最多的操作
- map的主要作用是对列表的每个元素执行自定义函数操作,返回B类型的集合(列表)
方法定义:def map[B](f: (A) ⇒ B): TraversableOnce[B]
B:B类型的集合,指定map方法最终返回的集合泛型
println(list.map((x:Int)=>x*10)) // ListBuffer(10, 20, 30, 40)
list.map(x=>x*10)
list.map(_*10)
- 扁平化映射 - flatMap
方法定义:def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): TraversableOnce[B]
该方法其本质是先进行了map 然后又调用了flatten
val list1 = List("hadoop hive spark flink", "hbase spark")// 返回列表
println(list1.flatMap(x => x.split(" ")))// List(hadoop, hive, spark, flink, hbase, spark)
过滤 - filter
方法定义:def filter(p: (A) ⇒ Boolean): TraversableOnce[A]
返回值:列表
val list = ListBuffer(1,2,3,4,5,6)
println(list.filter(x => x >5)) // ListBuffer(6)
println(list.filter(_ > 5).map(_ * 10)) // ListBuffer(60)
排序 - sort
- sorted默认排序
- sortBy指定字段排序
- sortWith自定义排序
val list = ListBuffer(3,2,1,4,5,6)
println(list.sorted)
val list1=List("3 hadoop","9 spark","4 flink")
println(list1.sortBy(x=>x.split(" ")(0))) // List(3 hadoop, 4 flink, 9 spark)
println(list.sortWith((x,y)=>x>y)) // 降序
println(list.sortWith((x,y)=>y<x)) // 降序
println(list.sortWith((x,y)=>y>x)) // 升序
println(list.sortWith((x,y)=>x<y)) // 升序 ListBuffer(1, 2, 3, 4, 5, 6)
println(list.sortWith(_<_)) // 简写,够简吧!
// 即传入比较大小的函数对象,函数体返回Boolean,为真则不改变参数顺序
分组 - groupBy
方法定义:def groupBy[K](f: (A) ⇒ K): Map[K, List[A]]
返回值:Map[K, List[A]] K为分组字段,List为这个分组字段对应的一组数据
val list = List("张三"->"男", "李四"->"女", "王五"->"男")
println(list.groupBy((kv:(String,String)) => {kv._2})) // Map(男 -> List((张三,男), (王五,男)), 女 -> List((李四,女)))
println(list.groupBy(_._2)) // 参数只出现一次,函数没有嵌套!丢掉传参,下划线代替,简写就行...
- 初学的你可能好奇这个list为什么是map的定义方式,别慌,这是一种元素类型“Key->Value”,并非map专有!
println(list.groupBy(_._2).map(x => x._1 -> x._2.size))// Map(男 -> 2, 女 -> 1)
// 注:键值对的索引从1开始
聚合 - reduce
- reduce表示将列表,传入一个函数进行聚合计算
val m = List(1,2,3,4,5,6,7,8,9,10)
println(m.reduce((x,y) => x + y)) // 累加,相当于m.sum=55
println(m.reduce(_ + _)) // 简写
println(m.reduceLeft(_ + _)) // 从左侧开始累加
println(m.reduceRight(_ + _)) // 从右侧开始累加
折叠 - fold
- fold与reduce相比,多了一个指定初始值参数
- 最终折叠为一个元素
val m = List(1,2,3,4,5,6,7,8,9,10)
println(m.fold(100)((x,y)=>x+y)) // 初始值为100
println(m.fold(100)(_+_))
println(m.foldLeft(100)((x,y)=>x+y))
println(m.foldRight(100)((x,y)=>x+y))
上面介绍的这些都是函数式编程的常见操作,用到的这些内置方法、函数表达式的运用都要熟练掌握,逐渐培养这样的编程思维!
高阶函数
- 使用函数值作为参数,或者返回值为函数值的“函数”和“方法”,均称之为“高阶函数”
val func=(x:Int)=>x*10
val array=Array(1,2,3,4,5)
array.map(func) // 函数作为参数
- 主要掌握以下几个概念
- 柯里化
方法可以定义多个参数列表,当调用多参数列表的方法时,内部会产生一个新的函数,该函数接收剩余的参数列表作为其参数
def getAddress(a:String)(b:String,c:String):String={
a+"-"+b+"-"+c
}
println(getAddress("china")("beijing","tiananmen")) // china-beijing-tiananmen
// 按照普通的写法应该是:柯里化会自动处理这个匿名函数为新函数
def getAddress(a:String):(String,String)=>String={
(b:String,c:String)=>a+"-"+b+"-"+c // 最后一行是方法的返回值,这里返回String
}
总结:意义在于把多参数列表的function等价转化成多个单参数列表的function的级联,这样所有的函数就都统一了,方便做lambda演算
- 闭包
函数里面引用外部变量叫作闭包
spark和flink程序的开发中大量的使用到函数,函数的返回值依赖的变量可能都需要进行大量的网络传输获取得到。这里就需要这些变量实现序列化进行网络传输
这也是函数作为变量表达式的优势所在,其他变量不在函数的作用域也可以调用
// 简单来说
var factor=10
val f1=(x:Int) => x*factor // 函数内部调用外部的参数
// 进阶版
def multi(x:Int) = (y:Int) => x*y // 方法的返回值是一个函数,使用val接收
val doubleFunc = multi(2) // 作用:根据参数得到不同功能的函数
val tribleFunc = multi(3)
println(doubleFunc(10)) // 20
println(tribleFunc(10)) // 30
总结:内部可以调用外部
结语
上面是scala中函数式编程的重点,深入的理解需要结合更多的数学知识,在大数据开发的学习过程中会逐步积累。
入门中的小白,如有不当之处烦请指出!
下一节介绍scala面向对象编程