1、高阶函数和闭包
1.1、定义函数
scala> val add = (x:Int,y:Int) => {x+y}
add: (Int, Int) => Int = <function2>
scala> val array = Array(1,2,3,4,5)
array: Array[Int] = Array(1, 2, 3, 4, 5)
scala> array.reduce(add)
res0: Int = 15
scala> val max = (x:Int,y:Int) => {if(x>y) x else y}
max: (Int, Int) => Int = <function2>
scala> array.reduce(max)
res1: Int = 5
1.1.1、匿名函数
scala> val array = Array(1,2,3,4)
array: Array[Int] = Array(1, 2, 3, 4)
scala> array.map((x:Int) => x + 1)
res0: Array[Int] = Array(2, 3, 4, 5)
scala> array.map(x => x + 1)
res1: Array[Int] = Array(2, 3, 4, 5)
scala> array.map(_ + 1)
res2: Array[Int] = Array(2, 3, 4, 5)
1.1.2、函数的各种写法
scala> val array = Array(1,2,3,4)
array: Array[Int] = Array(1, 2, 3, 4)
scala> array.map((x:Int) => x + 1)
res0: Array[Int] = Array(2, 3, 4, 5)
scala> array.map(x => x + 1)
res1: Array[Int] = Array(2, 3, 4, 5)
scala> array.map(_ + 1)
res2: Array[Int] = Array(2, 3, 4, 5)
scala> val array = Array(1,2,3,4)
array: Array[Int] = Array(1, 2, 3, 4)
scala> array.map((x:Int) => x+2)
res3: Array[Int] = Array(3, 4, 5, 6)
scala> array.map((x) => x+2)
res4: Array[Int] = Array(3, 4, 5, 6)
scala> array.map(x => x+2)
res5: Array[Int] = Array(3, 4, 5, 6)
scala> array.map(_+2)
res6: Array[Int] = Array(3, 4, 5, 6)
1.2、高阶函数
高阶函数主要有两种:
1、一种是将一个函数当做另外一个函数的参数(即函数的参数是函数)
2、另外一种是返回值是函数的函数(即函数的返回值是函数)
1.2.1、函数返回值为函数
作为返回值:一个方法的返回结果值是一个函数
scala> def add(x:Int) = {(y:Int) => x + y}
add: (x: Int)Int => Int
scala> val add2 = add(2)
add2: Int => Int = <function1>
scala> add2(3)
res7: Int = 5
1.2.2、函数/方法的参数为函数
作为参数:一个方法的参数不是一个值,而是一个函数,就是一个计算逻辑
scala> def opt(f:(Int,Int) => Int) = f(3,4)
opt: (f: (Int, Int) => Int)Int
scala> val add = (x:Int,y:Int) => x + y
add: (Int, Int) => Int = <function2>
scala> val max = (x:Int,y:Int) => if(x>y) x else y
max: (Int, Int) => Int = <function2>
scala> opt(add)
res8: Int = 7
scala> opt(max)
res9: Int = 4
简单总结:
函数是 Scala 中的头等公民:
1、 可以作为方法的返回值
2、 可以作为方法的参数
3、 方法也可以被转换为函数,特定场景下自动转换或者通过_手动转换
综合代码:
package com.jiangw.scala.oop
object Demo009_MethodAndFunction {
//定义一个方法
//方法m1参数要求一个函数,函数的参数必须是两个Int类型
//返回值类型也是Int类型
def m1(f:(Int,Int) => Int):Int = f(2,6)
//定义一个需要两个Int类型参数的方法
def m2(x:Int, y:Int):Int = x + y
//定义一个计算数据不被写死的方法
def m3(f:(Int,Int) => Int, x:Int, y:Int) : Int = f(x,y)
//定义一个函数f1,参数是两个Int类型,返回值是一个Int类型
val f1 = (x:Int, y:Int) => x + y
//在定义一个函数f2
val f2 = (x:Int, y:Int) => x * y
//定义一个传入函数的函数
val f3 = (f:(Int,Int) => Int, x:Int, y:Int) => f(x,y)
//main方法
def main(args: Array[String]): Unit = {
//调用m1方法,并传入f1函数
val r1 = m1(f1)
println(r1)
//调用m1方法,并传入f2函数
val r2 = m1(f2)
//调用m3方法,传入f1
val result1 = m3(f1,2,4)
println(result1)
//调用m3方法,传入f2函数
val result2 = m3(f2,2,4)
println(result2)
//调用m3方法,传入m2方法作为参数
println(m3(m2,2,4))
//调用f3函数,传入f1函数
println(f3(f1,3,4))
}
}
1.3、闭包
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数。
完整实例:
scala> var more = 9
more: Int = 9
scala> val add_more = (x:Int) => x + more
add_more: Int => Int = <function1>
scala> add_more(1)
res10: Int = 10
scala> more = 10
more: Int = 10
scala> add_more(1)
res11: Int = 11
在 add_more 中有两个变量:x 和 more。其中的一个 x 是函数的形式参数,在 add_more 函数被调用时,x 被赋予一个新的值。然而,more 不是形式参数,而是自由变量。这里我们引入一个自由变量 more,这个变量定义在函数外面。
这样定义的函数变量 add_more 成为一个"闭包",因为它引用到函数外面定义的变量,定义这个函数的过程是将这个自由变量捕获而构成一个封闭的函数。
像这种运行时确定 more 类型及值的函数称为闭包,more 是个自由变量,在运行时其值和类型得以确定这是一个由开放(free)到封闭的过程,因此称为闭包
有趣的 Scala 闭包的完整测试:
package com.jiangw.scala.oop
/**
* Scala编程语言的闭包测试
*/
object BiBaoTest {
def main(args: Array[String]): Unit = {
val result = bibao
result(2)
result(2)
result(2)
result(2)
}
val bibao = {
var sum = 0
val add_sum = (x:Int) => {
sum += x
println(sum)
}
add_sum
}
}
bibao是一个函数。但是这个函数的返回值是add_sum函数,其实result == add_sum,其实在调用add_sum的时候,sum变量的值被一直保存下来,因为add_sum和sum一样,都被保存在bibao这个函数的作用域中。
在上面这种代码情况下,bibao 这个函数中的 sum 变量,只有 bibao 内部的 add_sum 才能访问。但是 bibao 这个函数的返回值却是 add_sum 这个函数,这个函数使用了 sum 这个变量,所以使得这个变量在 main 方法中调用 result 函数的时候,其实做到了变量到的访问和修改了 bibao 函数中定义的变量,所以本质上,闭包就是将函数内部和函数外部的连接起来的桥梁。但是千万注意,这个 sum 变量会一直驻留内存。
参考资料:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
维基百科:https://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA %E7%A7%91%E5%AD%A6)
1.4、Scala柯里化Curry
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果是一个新函数的技术。有时需要允许他人一会在你的函数上应用一些参数,然后又应用另外的一些参数。例如一个乘法函数,在一个场景需要选择乘数,而另一个场景需要选择被乘数。所以柯里化函数就是将多个参数分开写,写在不同的小括号里,而不是在一个小括号中用逗号隔开。
示例代码:
package com.jiangw.scala.oop
object CurryTest {
def multiply(x:Int)(y:Int) = x*y
//柯里化就是把参数分开,把部分函数参数用下划线来代替
def multiply2 = multiply(2)_
//一个普通的方法,接受两个Int类型参数做乘积
def multiply3(x:Int,y:Int) = x*y
def multiply4(x:Int,y:Int=10) = x*y
def multiply5(x:Int)(y:Int=10) = x*y
def main(args: Array[String]): Unit = {
println(multiply(2)(4))
println(multiply2(4))
//跟柯里化的函数在结果上没有区别,那到底有什么区别呢?
println(multiply3(2,4))
println(multiply4(4))//报错
println(multiply4(4)())
}
}
那柯里化到底有什么作用?降低通用性,提高适用性
2、Scala隐式转换和隐式参数
隐式转换和隐式参数是 Scala 中两个非常强大的功能,利用隐式转换和隐式参数,你可以提供优雅的类库,对类库的使用者隐匿掉那些枯燥乏味的细节。
隐式的对类的方法进行增强,丰富现有类库的功能
是指那种以 implicit 关键字声明的带有单个参数的函数。
可以通过::implicit -v 这个命令显示所有做隐式转换的类。
2.1、Scala隐式转换探讨
现在我们来考虑一个问题:
之前讲过:
1 to 10 其实可以写成 1.to(10)
那其实就是表示:1 是一个 Int 类型的变量,所以证明 Int 类中会有一个 to 的方法
但事实上,我们在 Int 类型中根本就没有寻找 to 方法
那也就是说对一个 Int 类型的变量 1 调用 Int 类型不存在的一个方法,这怎么还能正常运行
呢?
原因就是隐式转换
看一个最简单的隐式转换的例子:
scala> val aa = 3.5
aa: Double = 3.5
scala> val aa:Int = 3.5
<console>:11: error: type mismatch;
found : Double(3.5)
required: Int
val aa:Int = 3.5
^
scala> implicit def double2Int(x:Double) = x.toInt
warning: there was one feature warning; re-run with -feature for details
double2Int: (x: Double)Int
scala> val x:Int = 3.5
x: Int = 3
那我们首先来看一下隐式参数:
2.2、隐式转换的发生时机
2.2.1、时机一:当调用某个对象不存在的方法时
2.2.2、时机二:当方法参数类型不匹配时
2.2.3、时机三:在视图边界的时候
2.3、隐式转换忠告
3、Scala泛型
3.1、Scala泛型基础
3.2、Scala类型变量界定
3.3、Scala视图界定
3.4、上界下界
3.4.1、上界
3.4.2、下界
3.5、Scala逆变和协変
3.5.1、协変
3.5.2、逆变
3.5.3、协変和逆变总结