Scala进阶之路-Scala函数篇详解
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.传值调用和传名调用
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.basicGrammar 7 8 object CalculateDemo { 9 10 /** 11 * 定义传值的方法 : 12 * add方法拥有2个Int类型的参数, 返回值为2个Int的和。 13 */ 14 def add(a: Int, b: Int) () = { 15 a + b 16 } 17 18 /** 19 * 定义传参的方法 : 20 * add2方法拥有3个参数,第一个参数是一个函数(它拥有2个Int类型的参数,返回值为Int类型的函数), 第二个,第三个为Int类型的参数 21 */ 22 def add2(f:(Int, Int) => Int, a: Int, b: Int) = { 23 f(a, b) // f(1, 2) => 1 + 2 24 } 25 26 /** 27 * 定义传参的方法 : 28 * 第一个参数是一个函数(它拥有1个Int类型的参数,返回值为Int类型的函数),第二个为Int类型的参数。 29 */ 30 def add3(a:(Int) => Int, b: Int) = { 31 //这里我们将第二个参数作为第一个函数的签名传递进去 32 a(b) + b 33 } 34 /** 35 * fxx你们函数是符合add2中的“f:(Int, Int) => Int”这个方法签名的,因此我们可以把它当做第一个参数进行传递 36 */ 37 val f1 = (a: Int, b: Int) => a + b 38 39 /** 40 * 定义一个匿名函数,它需要传递一个参数,函数体的返回值是将传入的值乘以10并返回,返回值类型为Int。 41 */ 42 val f2 = (x: Int) => (x * 10):Int 43 44 def main(args: Array[String]): Unit = { 45 46 //传值方式调用 47 val res1 = add(100, 10 + 20) 48 println(res1) 49 50 //传参方式调用一,我们给匿名函数传参数,最终返回的结果和第二个参数以及第三个参数进行运算 51 var res2 = add(f1(10, 20), 30) 52 println(res2) 53 54 //传参方式调用二,我们给匿名函数传参数, 55 var res3 = add2(f1, 10, 20) 56 println(res3) 57 58 //传参方式调用 59 val res4 = add3(f2, 8) 60 println(res4) 61 } 62 } 63 64 65 66 /* 67 以上代码执行结果如下 : 68 130 69 60 70 30 71 88 72 */
二.可变参数
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.basicGrammar 7 8 object VariableParameter { 9 10 /** 11 * 定义一个可变参数的方法,参数的类型为任意类型,相当于Java中的Object类,当然你也可以为AnyVal或者AnyRef的子类 12 */ 13 def methodManyParams(args: Any*) = { 14 for (item <- args) { 15 print(item + "|") 16 } 17 println() 18 } 19 20 /** 21 * 可变参数一般放在参数列表的末尾。 22 */ 23 def add(des:String, ints:Int*):Int = { 24 var sum = 0 25 for (value <- ints){ 26 sum += value 27 } 28 print(des) 29 sum 30 } 31 32 def main(args: Array[String]): Unit = { 33 methodManyParams("尹正杰", "大数据", "云计算", "人工智能", "机器学习", "自动化运维",2018) 34 35 var res = add("计算结果 : ", 10, 20, 30, 40) 36 println(res) 37 38 } 39 } 40 41 42 43 44 45 /* 46 以上代码执行结果如下 : 47 尹正杰|大数据|云计算|人工智能|机器学习|自动化运维|2018| 48 计算结果 : 100 49 */
三.参数的默认值
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.basicGrammar 7 8 object DefaultValuesParameters { 9 10 /** 11 * 参数定义时可以指定一个默认值 12 * @param path : 指定程序的安装路径 13 * @param user : 指定安装的用户 14 */ 15 def installationSoftware(path:String = "D:/yinzhengjie/BigData/Spark",user:String="yinzhengjie") ={ 16 print(s"安装路径是: $path,当然的安装用户是 : ${user} ") 17 } 18 19 def main(args: Array[String]): Unit = { 20 //调用时如果不传递参数,就会使用函数或者方法的默认值 21 installationSoftware() 22 //调用时,如果传递了参数值,就会使用传递的参数值 23 installationSoftware("E:/yinzhengjie/Hadoop/Scala","Administrator") 24 //调用时,如果传递了一个参数,那么就会覆盖第一个参数的值 25 installationSoftware("/home/yinzhengjie/Spark/Scala") 26 //如果想要给指定的参数赋值,可以采用键值对的方式赋值,赋值参数时,参数的名称和方法定义的名称需要保持一致! 27 installationSoftware(user = "root") 28 //当然赋值的方式可以打乱顺序,但是需要以键值对的方式传递哟! 29 installationSoftware(user = "Scala",path = "/home/yinzhengjie/Hadoop/Spark") 30 } 31 } 32 33 34 35 /* 36 以上代码执行结果如下 : 37 安装路径是: D:/yinzhengjie/BigData/Spark,当然的安装用户是 : yinzhengjie 38 安装路径是: E:/yinzhengjie/Hadoop/Scala,当然的安装用户是 : Administrator 39 安装路径是: /home/yinzhengjie/Spark/Scala,当然的安装用户是 : yinzhengjie 40 安装路径是: D:/yinzhengjie/BigData/Spark,当然的安装用户是 : root 41 安装路径是: /home/yinzhengjie/Hadoop/Spark,当然的安装用户是 : Scala 42 */
四.高阶函数
高阶函数的定义:将其他函数作为参数或其结果是函数的函数。要注意的是传入的函数必须符合高阶函数参数中定义的函数签名。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.basicGrammar 7 8 object HigherFunction { 9 /** 10 * 定义一个高级函数: 11 * f: (Int) => String 12 * 第一个参数是带一个整型参数返回值为字符串的函数f 13 *v: Int 14 * 第二个参数是一个整型参数v 15 * f(v) 16 * 返回值为一个函数“f(v)”。 17 */ 18 def apply(f: (Int) => String, v: Int) = { 19 //返回的函数“f(v)”,即将第二个参数作为第一个函数的参数。 20 f(v) 21 } 22 23 // 定义一个方法, 参数为一个整型参数, 返回值为String 24 def layout(args: (Int)):String = { 25 "[" + args.toString() + "]" 26 } 27 28 def main(args: Array[String]): Unit = { 29 //注意,layout传入的参数个数以及返回值类型都必须符合高阶函数apply中定义的第一个参数的函数签名。 30 println (apply (layout , 150)) 31 } 32 } 33 34 35 36 /* 37 以上代码执行结果如下: 38 [150] 39 */
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.basicGrammar 7 8 object HigherFunctionDemo { 9 10 def op(f:(Int,Int)=>Int,a:Int,b:Int):Int = { 11 f(a , b) 12 } 13 14 def add(a:Int,b:Int):Int = { 15 a + b 16 } 17 18 def sub(a:Int,b:Int):Int = { 19 a - b 20 } 21 22 def main(args: Array[String]): Unit = { 23 val res1 = op(add,10,2) 24 val res2 = op(sub,10,2) 25 26 print(s"res1=====> ${res1} ") 27 print(s"res2=====> ${res2} ") 28 } 29 } 30 31 /* 32 以上代码执行结果如下 : 33 res1=====> 12 34 res2=====> 8 35 */
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.basicGrammar 7 8 object HigherFunctionDemo2 { 9 10 /** 11 * 该函数用于计算幂函数 12 * powx : 13 * 这个是定义高阶函数的名称。 14 * n:Int : 15 * 这个是高阶函数的参数。 16 * (Double)=>Double : 17 * 这个是高阶函数返回的匿名函数签名,第一个“(Double)”表示匿名函数的参数,第二个“Double”表示的是返回值类型。 18 * (x:Double) => 19 * 从该行往后都是匿名函数的函数体。 20 */ 21 def powx(n:Int): (Double)=>Double = { 22 (x:Double) => { 23 var sum :Double = 1 //定义Double类型的变量sum 24 for(i <- 1 to n) sum *= x //定义运算过程 25 sum //返回计算的结果 26 }:Double //返回值类型为:Double,我们也可以省略不写哟 27 } 28 29 def main(args: Array[String]): Unit = { 30 //计算4的二次幂 31 val res1 = powx(2)(4) 32 println(res1) 33 } 34 } 35 36 37 38 /* 39 以上代码执行结果如下: 40 16.0 41 */
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.function 7 8 object LinearAlgebra { 9 /** 10 * 定义线性代数的高阶函数,模拟:y = ax + b 11 */ 12 def line(x1:(Int,Int)=>Int,x2:Int,x3:Int,x4:(Int,Int)=>Int,x5:Int,x6:Int):Int=>Int={ 13 val a = x1(x2,x3) 14 val b = x4(x5,x6) 15 def f(args:Int):Int =a * args +b 16 //返回函数 17 f _ 18 } 19 20 /** 21 * 上面的也可以写成一行,就是返回匿名函数,具体代码如下: 22 */ 23 def line2(x1:(Int,Int)=>Int,x2:Int,x3:Int,x4:(Int,Int)=>Int,x5:Int,x6:Int):Int=>Int={ 24 //我们也可以返回匿名函数 25 (x:Int) =>{ 26 x1(x2,x3) * x + x4(x5,x6) 27 } 28 } 29 30 def add(x:Int,y:Int) = x + y 31 32 def sub(x:Int,y:Int) = x - y 33 34 def main(args: Array[String]): Unit = { 35 //调用函数 36 var res1 = line(add,1,2,sub,3,4)(5) 37 var res2 = line2(add,1,2,sub,3,4)(5) 38 print(s"res1 =====> ${res1} ") 39 print(s"res2 =====> ${res2} ") 40 } 41 } 42 43 44 45 /* 46 以上代码执行结果如下: 47 res1 =====> 14 48 res2 =====> 14 49 */
高阶有用的函数用法展示:
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.basicGrammar 7 8 object HigherFunction { 9 def main(args: Array[String]): Unit = { 10 var res1 = (1 to 10).map(_ * 0.1) 11 print(s"res1 =====> $res1 ") 12 println("==========我是分割线=========") 13 (1 to 10).map("*" * _).foreach(println) 14 println("==========我是分割线=========") 15 val arr = Array[Int](1,2,3,4) 16 val res2 = arr.reduceLeft(_ - _) 17 val res3 = arr.reduceRight(_ - _) 18 println("==========我是分割线=========") 19 print(s"res2 =====> $res2 ") 20 println("==========我是分割线=========") 21 print(s"res3 =====> $res3 ") 22 } 23 } 24 25 26 /* 27 以上代码执行结果如下: 28 res1 =====> Vector(0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1.0) 29 ==========我是分割线========= 30 * 31 ** 32 *** 33 **** 34 ***** 35 ****** 36 ******* 37 ******** 38 ********* 39 ********** 40 ==========我是分割线========= 41 ==========我是分割线========= 42 res2 =====> -8 43 ==========我是分割线========= 44 res3 =====> -2 45 */
五.部分参数应用函数
部分参数应用函数定义:如果函数传递所有预期的参数,则表示已完全应用它。如果只传递几个参数并不是全部参数,那么将返回部分应用的函数。这样就可以方便地绑定一些参数,其余的参数可稍后填写补上。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.basicGrammar 7 8 import java.util.Date 9 10 11 object PartialParameter { 12 13 /** 14 * 定义个输出的方法, 参数为date, message 15 */ 16 def log(date: Date, message: String) = { 17 println (s"$date, $message") 18 } 19 20 def main(args: Array[String]): Unit = { 21 //定义一个日期对象 22 val date = new Date() 23 /** 24 * 调用log 的时候, 传递了一个具体的时间参数, message为待定参数。logBoundDate成了一个新的函数,它需要传递一个String对象。 25 * 26 */ 27 val logBoundDate : (String) => Unit = { 28 //我们在调用log函数时,值传递了第一个参数,第二个参数我们空出来了,并没有传递,而是指定第二个参数的类型。 29 log (date , _: String) 30 } 31 32 // 调用logBoundDate 的时候, 只需要传递待传的message 参数即可 33 logBoundDate ("I'm Yinzhengjie!") 34 35 //当然你想要传递两个参数,直接调用log函数也是可以的哟! 36 log(date,"I'm Yinzhengjie") 37 } 38 } 39 40 41 /* 42 以上代码执行结果如下 : 43 Mon Jul 23 15:56:44 CST 2018, I'm Yinzhengjie! 44 Mon Jul 23 15:56:44 CST 2018, I'm Yinzhengjie 45 */
六.柯里化函数(Currying)
柯里化(Currying,以逻辑学家Haskell Brooks Curry的名字命名)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。因此我们可以说柯里化就是高阶函数的一种,而高阶函数不一定就是柯里化函数。
柯里化的好处:有时候,你想要用柯里化来把某个函数参数单拎出来,以提供更多用于类型推断的信息。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.function 7 8 object MyCurrying { 9 10 /** 11 * 常规方法求两个参数和的函数: 12 * 我们看下这个方法的定义, 求2个数的和,需要传递两个参数 13 */ 14 def add1(x: Int, y: Int) = x + y 15 16 /** 17 * 现在我们把上面的函数变一下形 : 18 * 使用柯里化(Currying)两个参数和的函数: 19 */ 20 def add2(x:Int)(y:Int) = x + y 21 22 /** 23 * 分析下其演变过程 : 24 * (y: Int) => x + y 为一个匿名函数, 也就意味着 add3 方法的返回值为一个匿名函数. 25 */ 26 def add3(x: Int) = (y: Int) => x + y 27 28 def main(args: Array[String]): Unit = { 29 30 var res1 = add1(10,20) 31 println(res1) 32 33 /** 34 * 这种方式(过程)就叫柯里化。经过柯里化之后,函数的通用性有所降低,但是适用性有所提高。 35 */ 36 var res2 = add2(10)(20) 37 println(res2) 38 39 /** 40 * 调用方式需要进行两次传值操作,有点类似我们之前说的部分参数应用函数 41 */ 42 val res3 = add3(10) 43 print(res3(20)) 44 } 45 } 46 47 /* 48 以上代码输出结果如下: 49 30 50 30 51 30 52 */
七.偏函数
被包在花括号内没有 match 的一组 case 语句是一个偏函数,它是 PartialFunction[-A, +B]的一个实例,“-A” 代表参数类型,“+B” 代表返回类型,常用作输入模式匹配。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.function 7 8 object MyPartialFunction { 9 10 /** 11 * 定义一个函数,要求传入的参数是一个String类型,而返回值类型是Int类型 12 */ 13 def func(language:String):Int = { 14 if (language.equals("Python")) 100 15 else if (language.equals("Golang")) 200 16 else if (language.equals("Java")) 300 17 else if (language.equals("Shell")) 400 18 else if (language.equals("Scala")) 500 19 else -1 20 } 21 22 /** 23 * 上面的函数我们也可以用关键字match+case组合来匹配用户的输入值 24 */ 25 def func2(num: String) : Int = num match { 26 //case 可以匹配传进来的参数,即传入的字符串是"Python"就返回100. 27 case "Python" => 100 28 case "Golang" => 200 29 case "Java" => 300 30 case "Shell" => 400 31 case "Scala" => 500 32 //case _ 表示匹配默认情况,即以上条件均不满足时会走下面的这个语句哟 33 case _ => -1 34 } 35 36 /** 37 * 接下来我们用偏函数重写上面的func函数的功能 38 * 其中PartialFunction就是偏函数的关键字,里面的第一个参数是调用者输入参数的类型(String),而第二个参数是返回值类型(Int类型) 39 */ 40 def func3:PartialFunction[String,Int] = { 41 //case 可以匹配传进来的参数,即传入的字符串是"Python"就返回100. 42 case "Python" => 100 43 case "Golang" => 200 44 case "Java" => 300 45 case "Shell" => 400 46 case "Scala" => 500 47 //case _ 表示匹配默认情况,即以上条件均不满足时会走下面的这个语句哟 48 case _ => -1 49 } 50 51 52 def func4:PartialFunction[Any,Int]={ 53 //case也可以匹配传进来的类型,如果是Int类型就将这个参数乘以2并返回,如果这个参数不是Int类型的话就返回-1 54 case i:Int => i * 2 55 case _ => -1 56 } 57 58 def main(args: Array[String]): Unit = { 59 60 var res1 = func("Python") 61 println(res1) 62 63 var res2 = func2("Python") 64 println(res2) 65 66 var res3 = func3("Python") 67 println(res3) 68 69 var arr = Array[Any](1,"yinzhengjie",3,"尹正杰",5) 70 var res4 = arr.collect(func4) 71 println(res4.toBuffer.toString()) 72 } 73 } 74 75 76 77 /* 78 以上代码输出结果如下 : 79 100 80 100 81 100 82 ArrayBuffer(2, -1, 6, -1, 10) 83 */