1 摘要
本章博客主要探讨了,Scala语言中的下划线的用法。作为初学者,这个用法很隐晦。但在Scala中这种写法会让代码变得简介高效且容易修改,所以好好学习一下是非常有必要的。很好的参考材料
(https://stackoverflow.com/questions/8000903/what-are-all-the-uses-of-an-underscore-in-scala)
2 参数的占位符
Scala语言中,用(_)来表示一个函数的参数,注意:只有在你打算引用的这个函数值中只引用这个参数一次时可以这样做,可以在一个函数值中多次使用下划线,但是每个下划线都表示相继的不同的参数,下面有一些例子:
2.1、Placeholder syntax(占位符,函数参数的占位符)
正确的示范:
因为传入的匿名函数只使用了一次,所以写成:(1 to 9).map(_*2)
错误的示范:
如果要映射成x=>(x,x*x),比如:1->(1,1),2->(2,4)等于元素的集合,可以按照下面方式写:(1 to 9).map(a=>(a,a*a)),如下写成下面就是错误的示范
总结:传入的匿名函数参数只使用了一次,就可以用下划线代替。其实这样能让代码精简,尤其实在传入的匿名函数的参数值使用一次的时候,这个时候给这个变量命名就显示不那么重要了,所以在合适的地方使用(_)。
2.2 Partially applied functions(偏应用函数)
调用一个函数,实际上是在一些参数上应用这些函数。如果传递了所有期望的参数,就是对这个函数的完整应用,如果传递的参数比所要求的参数少,就会得到另外一个函数,就被成为偏应用函数。
1 /** 2 * 3 * @param d 时间 4 * @param log 日志信息 5 */ 6 def showLog(d: Date, log: String): Unit = { 7 println("时间:" + d, ",日志信息:" + log) 8 } 9 val date = new Date() 10 //偏应用函数 11 showLog(date: Date, _: String)
下划线表示部分传入参数。
2.3 参数路由
列举了Spark Streaming中updateStateByKey算子的使用来说明(_)能表示整个参数列表
是一个将函数值变得简单的方法,
1 def main(args: Array[String]): Unit = { 2 val sc = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount") 3 val ssc = new StreamingContext(sc, Duration(5000)) 4 ssc.checkpoint("./") 5 val lines = ssc.socketTextStream("39.96.7.174", 1884) 6 val result = lines.flatMap(_.split(" ")).map((_, 1)) 7 val state = result.updateStateByKey(updateFunction _) 8 state.print() 9 ssc.start() 10 ssc.awaitTermination() 11 } 12 13 def updateFunction(currentValue: Seq[Int], preValue: Option[Int]): Option[Int] = { 14 val sum = currentValue.sum 15 val pre = preValue.getOrElse(0) 16 Some(sum + pre) 17 }
spark中updatestateBykey算子的使用中,第七行表示:_表示传入了整个参数列表,所以对updateFunction()的调用能写入上面的方式。
3 spark 算子中
3.1 map、flatMap、reduceBykey简写
未简化:
1 val conf = new SparkConf() 2 conf.setMaster("local[2]").setAppName("") 3 val sc = new SparkContext(conf) 4 // sc.textFile("").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).foreach(println()) 5 val lines: RDD[String] = sc.textFile("") 6 val words: RDD[String] = lines.flatMap(line => { 7 line.split(" ") 8 }) 9 val pairwords: RDD[(String, Int)] = words.map(word => { 10 new Tuple2(word, 1) //new Tuple可以省略 11 }) 12 val result = pairwords.reduceByKey((v1: Int, v2: Int) => { 13 v1 + v2 14 }) 15 result.foreach(tuple => { 16 println(tuple) 17 }) 18 sc.stop()
简化后:
1 val conf = new SparkConf() 2 conf.setMaster("local[2]").setAppName("") 3 val sc = new SparkContext(conf) 4 sc.textFile("").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).foreach(println()) 5 sc.stop()
在flatMap、map、reduceByKey中传入的匿名函数只使用了一次,例如:line => { line.split(" "),所以可以用下划线代替,简写为:_.split(" ")。
4 总结
总结用法如下:
- 作为包引入通配符。例如scala中import java.util._等同于Java中的import java.util.*
- 作为元祖索引的前缀。对于给定的元祖,可以用._1和._2来分别索引相应的值
- 作为函数的隐式参数。如:list.map{_*2}和list.map{e=>e*2}是等价的
- 用于默认值初始化变量。var min :Int=_将使用0初始化变量min。
- 用于在函数名中混合操作符。待补充,目前还没遇到。
- 在进行模式匹配作为通配符。在使用模式匹配的时候,case_将会匹配任意给定的值,case_:Int将会匹配任何整数。
- 在处理异常时候,在catch代码块中和case联用
- 作为分解操作的一部分。例如,max(args:_*)在将数组或者列表参数传递给接受可变长度参数的函数前,将其分解为离散的值
- 用于偏应用函数。前面有详细介绍
注意调整Scala代码到一个折中的程度,以达到对可读性的要求,我们在让Scala代码简洁的同时,不能让代码的含义变得模糊。