Scala入门系列(九):函数式编程
引言
Scala是一门既面向对象,又面向过程的语言,Scala的函数式编程,就是Scala面向过程最好的佐证。也真是因此让Scala具备了Java所不具备的更强大的功能和特性。
而之所以Scala一直没有替代Java,一是因为Java诞生早,基于Java开发了大量知名的工程,并且最重要的是Java现在不只是一门编程语言,而是一个庞大的技术生态圈,所以未来十年内Scala也不会完全替代Java,但是Scala会在自己特有的领域大发光彩。
将函数赋值给变量
Scala中函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量。
Scala语法规定:将函数赋值给变量时,必须在函数后加上空格和下划线
scala> def sayHello(name: String) = println("hello, " + name) 
sayHello: (name: String)Unit 
// 必须在函数后加上空格和下划线 
scala> val sayHelloFunc = sayHello _ 
sayHelloFunc: String => Unit = <function1> 
 
scala> sayHelloFunc("sparks") 
hello, sparks 
匿名函数
可以直接定义匿名函数并赋值给某个变量;也可以将匿名函数传入其他函数之中。
Scala定义匿名函数的语法规则是,(参数名: 参数类型) => {函数体}
匿名函数在Spark源码中有大量使用。
scala> val sayHelloFunc = (name: String) => {println("hello, "+name)} 
sayHelloFunc: String => Unit = <function1> 
 
scala> sayHelloFunc("sparks") 
hello, sparks 
高阶函数
在Scala中,由于函数时一等公民,因此可以直接将某个函数传入其他函数,作为参数。这个功能是极其强大的,也是Java这种面向对象的编程语言所不具备的。
接收其他函数作为参数的函数,也被称为高阶函数(higher-order function)
scala> val sayHelloFunc = (name: String) => println("hello, "+name) 
 
// 接收函数作为参数 
scala> def greeting(func: (String)=> Unit, name: String){func(name)} 
 
// 将匿名函数当做参数传入 
scala> greeting(sayHelloFunc, "leo") 
hello, leo 
 
// Array的map函数也是高阶函数 
scala> Array(1,2,3,4,5).map((num: Int) => num*num) 
res5: Array[Int] = Array(1, 4, 9, 16, 25) 
 
// 高阶函数的另外一个功能是将函数作为返回值 
scala> def getGreetingFunc(msg: String) = (name: String) => println(msg + " " + name) 
 
scala> val greetingFunc = getGreetingFunc("hello") 
 
scala> greetingFunc("sparks") 
hello sparks 
高阶函数-类型推断(更易阅读)
在高阶函数中,经常将只需要执行一次的函数定义为匿名函数作为参数传递给高阶函数,就好像map()、filter()等高阶函数中经常能看到使用了匿名函数作为参数。匿名函数在这里有一个特性能够帮助我们写出更容易阅读的函数——参数推断。 
正常情况下,我们使用匿名函数的方式如下:
object Test { 
  def main(args: Array[String]) { 
    val arr = Array(1.23, 34.1, 21.32) 
    val result = arr.map((a: Double) => a * 3) 
    println(result.mkString(" ")) 
  } 
} 
即在map函数中定义匿名函数(a: Double) => a * 3,但是由于map函数知道你传入的是一个类型为(Double)=> Double类型的函数,故可以简化为
object Test { 
  def main(args: Array[String]) { 
    val arr = Array(1.23, 34.1, 21.32) 
    val result = arr.map((a) => a * 3) 
    println(result.mkString(" ")) 
  } 
} 
并且如果匿名函数只有一个参数,则可以省略(),继续简化:
object Test { 
  def main(args: Array[String]) { 
    val arr = Array(1.23, 34.1, 21.32) 
    val result = arr.map(a => a * 3) 
    println(result.mkString(" ")) 
  } 
} 
在此基础上,如果参数在=>右边只出现了一次,则可以用_替换它(Spark中大量使用了这种语法):
object Test { 
  def main(args: Array[String]) { 
    val arr = Array(1.23, 34.1, 21.32) 
    val result = arr.map(_ * 3) 
    println(result.mkString(" ")) 
  } 
} 
  
类似的示例:
// 定义该函数参数接收Int参数,返回Int值 
scala> def triplt(func: (Int) => Int) = {func(3)} 
// 3作为该匿名参数的唯一参数与6做了相乘,然后返回 
// 相当于triplt((a: Int) => a * 6) 
scala> triplt(_ * 6) 
res17: Int = 18 
 
scala> triplt(_ + 6) 
res18: Int = 9 
常见的高阶函数
- map:对传入集合的每个元素进行映射,返回每个元素处理后的集合。
scala> Array(3,4,1,5,2).map(2 * _) 
res23: Array[Int] = Array(6, 8, 2, 10, 4) 
- foreach:对传入的每个元素都进行处理,但是没有返回值。
scala> (1 to 9).map("*" * _).foreach(println _) 
* 
** 
*** 
**** 
***** 
****** 
******* 
******** 
********* 
- filter:对传入的每个元素都进行条件判断,保留True元素。
scala> (1 to 20).filter(_ % 2 == 0) 
res20: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) 
- reduceLeft:从左侧开始,即先对参数0和参数1进行处理,然后将结果与元素2处理,再将结果与元素3处理,一次类推,即为reduce;(Spark编程的重点!)
// 相当于1 * 2 * 3 *……* 9 
scala> (1 to 9).reduceLeft(_ * _) 
res21: Int = 362880 
 
// 注意:reduceLeft的中间结果是传递给第一个参数的 
scala> List(1,2,3,4).reduceLeft{ (m, n) => println(m + ": " + n); m - n} 
1: 2 
-1: 3 
-4: 4 
res77: Int = -8 
 
// 注意:reduceRight的中间结果是传递给第二个参数的 
scala> List(1,2,3,4).reduceRight{ (m, n) => println(m + ": " + n); m - n} 
3: 4 
2: -1 
1: 3 
res78: Int = -2 
- foldLeft:类似于reduceLeft,不过flod提供一个初始值。
scala> List(1,2,3,4).foldLeft(10) { (m, n) => println(m + ": " + n); m - n} 
10: 1 
9: 2 
7: 3 
4: 4 
res73: Int = 0 
 
scala> List(1,2,3,4).foldRight(10) { (m, n) => println(m + ": " + n); m - n} 
4: 10 
3: -6 
2: 9 
1: -7 
res74: Int = 8 
- 对元素进行两两比较,排序
scala> Array(3,4,1,5,2).sortWith(_ < _) 
res22: Array[Int] = Array(1, 2, 3, 4, 5) 
- collect操作,结合偏函数使用
scala> "abc".collect{ case 'a' => 1; case 'b' => 2; case 'c' => 3 } 
res66: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) 
闭包
函数在变量不处于其有效作用域时,还能够对变量进行访问,即为闭包
scala> def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name) 
 
scala> val greetingFuncHello = getGreetingFunc("hello") 
 
scala> val greetingFuncHi = getGreetingFunc("hi") 
 
scala> greetingFuncHello("sparks") 
hello, sparks 
 
scala> greetingFuncHi("sparks") 
hi, sparks 
在上面的getGreetingFunc函数中,msg只是一个局部变量,却在getGreetingFunc执行完后,还可以继续存在创建的函数之中。
greetingFuncHello(“sparks”)运行时,还值为hello的msg被保留在了函数体内部,可以反复使用。
这种变量超出了其作用域,还能够对变量进行访问,即为闭包。
原理
Scala通过为每个函数创建对象来时间闭包,实际上对于greetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同msg。
SAM转换
在Java中,不支持直接将函数当做参数传入方法,唯一的办法就是定义一个实现了某个接口的类的实例对象,该对象只有一个方法;而这些接口都只有单个的抽象方法,也就是single abstract method, 简称为SAM
由于Scala是可以调用Java代码的,因为当我们调用Java的某个方法时,可能就不得不创建SAM传递给方法,非常麻烦;但是Scala又是支持直接传递函数的,此时就可以使用Scala提供的隐式转换特性将SAM转化为Scala函数。
import javax.swing._ 
import java.awt.event._ 
// 标准的JAVA SAM写法 
val button = new JButton("Click") 
button.addActionListener(new ActionListener { 
  override def actionPerformed(event: ActionEvent){ 
    println("Click me") 
  } 
}) 
// 直接传入函数参数报错,类型不匹配 
scala> button.addActionListener{(event: ActionEvent) => println("click me")} 
<console>:15: error: type mismatch; 
 found   : java.awt.event.ActionEvent => Unit 
 required: java.awt.event.ActionListener 
              button.addActionListener{(event: ActionEvent) => println("click me")} 
// 隐式转换 
implicit def getActionListener(actionProcessFunc: (ActionEvent) => Unit) = new ActionListener{ 
  override def actionPerformed(event: ActionEvent){ 
    actionProcessFunc(event) 
  } 
} 
// 隐式转换后再传函数参数就ok了 
scala> button.addActionListener{(event: ActionEvent) => println("click me")} 
Currying函数
定义:将原来接收两个参数的一个函数,转换为两个函数。 
第一个函数接收原先的第一个参数,然后返回接收原先第二个参数的第二个函数。 
在函数调用的过程中,就变成了两个函数连续调用的形式。 
在Spark的源码中也使用了此特性。
scala> def sum(a: Int, b: Int) = a + b 
scala> sum(1,1) 
res30: Int = 2 
// Currying函数原理 
scala> def sum2(a: Int) = (b: Int) => a + b 
scala> sum2(1)(1) 
res31: Int = 2 
// Scala提供的更加简洁的语法 
scala> def sum3(a: Int)(b: Int) = a + b 
scala> sum3(1)(1) 
res32: Int = 2 
return
在Scala中,不需要使用return来返回函数值,但是如果要使用的话,该函数必须要给出返回类型,否则无法通过编译。
def functionName ([参数列表]) : [return type] = { 
   function body 
   return [expr] 
} 
 
object add{ 
   def addInt( a:Int, b:Int ) : Int = { 
      var sum:Int = 0 
      sum = a + b 
 
      return sum 
   } 
}
Scala入门系列(九):函数式编程的更多相关文章
- Scala入门系列(十):函数式编程之集合操作
		1. Scala的集合体系结构 Scala中的集合体系主要包括(结构跟Java相似): Iterable(所有集合trait的根trait) Seq(Range.ArrayBuffer.List等) ... 
- Scala - 快速学习09 - 函数式编程:一些操作
		1- 集合类(collection) 系统地区分了可变的和不可变的集合. scala.collection包中所有的集合类 可变集合(Mutable) 顾名思义,意味着可以修改,移除或者添加一个元素. ... 
- Scala - 快速学习08 - 函数式编程:高阶函数
		函数式编程的崛起 函数式编程中的“值不可变性”避免了对公共的可变状态进行同步访问控制的复杂问题,能够较好满足分布式并行编程的需求,适应大数据时代的到来. 函数是第一等公民 可以作为实参传递给另外一个函 ... 
- Scala入门系列(三):数组
		Array 与Java的Array类似,也是长度不可变的数组,此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组. 注意:访问数组中元素使 ... 
- Java基础复习笔记系列 九 网络编程
		Java基础复习笔记系列之 网络编程 学习资料参考: 1.http://www.icoolxue.com/ 2. 1.网络编程的基础概念. TCP/IP协议:Socket编程:IP地址. 中国和美国之 ... 
- [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念
		本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程? java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ... 
- JavaScript系列:函数式编程(开篇)
		前言: 上一篇介绍了 函数回调,高阶函数以及函数柯里化等高级函数应用,同时,因为正在学习JavaScript·函数式编程,想整理一下函数式编程中,对于我们日常比较有用的部分. 为什么函数式编程很重要? ... 
- Scala入门系列(十二):隐式转换
		引言 Scala提供的隐式转换和隐式参数功能,是非常有特色的功能.是Java等编程语言所没有的功能.它可以允许你手动指定,将某种类型的对象转换成其他类型的对象.通过这些功能可以实现非常强大而且特殊的功 ... 
- Scala入门系列(一):基础语法
		Scala基础语法 Scala与JAVA的关系 Scala是基于Java虚拟机,也就是JVM的一门编程语言,所有Scala的代码都需要经过编译为字节码,然后交由Java虚拟机来运行. 所以Scala和 ... 
随机推荐
- .9-Vue源码之AST(5)
			上节跑完了超长的parse函数: // Line-9261 function baseCompile(template, options) { // Done! var ast = parse(tem ... 
- 2017年编程语言排行榜Top10,第一名是?
			关注 最近,IEEE Spectrum 杂志(美国电气电子工程师学会出版的旗舰杂志)发布了一年一度的编程语言排行榜,这也是他们发布的第四届编程语言 Top 榜.据介绍,IEEE Spectrum 的排 ... 
- sklearn中各算法类的fit,fit_transform和transform函数
			在使用PCA和NFC中有三个函数fit,fit_transform,transform区分不清各自的功能.通过测试,勉强了解各自的不同,在这里做一些笔记. 1.fit_transform是fit和tr ... 
- ①bootstrap引入
			<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ... 
- 逆向知识第八讲,if语句在汇编中表达的方式
			逆向知识第八讲,if语句在汇编中表达的方式 一丶if else的最简单情况还原(无分支情况) 高级代码: #include "stdafx.h" int main(int argc ... 
- Java中Date日期字符串格式的各种转换
			public class DateParserT { /** * Date 与 String.long 的相互转换 * @param args ... 
- 面向亿万级用户的QQ一般做什么?——兴趣部落的 Web 同构直出分享
			欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:李强,腾讯web开发工程师商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处.原文链接:http://wetest.qq.co ... 
- iOS开发中获取视图在屏幕上显示的位置
			在iOS开发中,我们会经常遇到一个问题,例如,点击一个按钮,弹出一个遮罩层,上面显示一个弹框,弹框显示的位置在按钮附近.如果这个按钮的位置相对于屏幕边缘的距离是固定的,那就容易了,可以直接写死位置.可 ... 
- django作业2
			管理后台 1.登陆Form 2.Session (用装饰器实现) 3.装饰器 4.主机,主机组 添加(主机,主机组) 删除 修改 查询 
- java并发之线程同步(synchronized和锁机制)
			使用synchronized实现同步方法 使用非依赖属性实现同步 在同步块中使用条件(wait(),notify(),notifyAll()) 使用锁实现同步 使用读写锁实现同步数据访问 修改锁的公平 ... 
