一、函数
1.1 函数与方法
Scala中函数与方法的区别非常小,如果函数作为某个对象的成员,这样的函数被称为方法,否则就是一个正常的函数。
1 2 3 4 5 6 7
| def multi1(x:Int) = {x * x}
val multi2 = (x: Int) => {x * x}
println(multi1(3)) println(multi2(3))
|
也可以使用def
定义函数:
1 2
| def multi3 = (x: Int) => {x * x} println(multi3(3))
|
multi2
和multi3
本质上没有区别,这是因为函数是一等公民,val multi2 = (x: Int) => {x * x}
这个语句相当于是使用def
预先定义了函数,之后赋值给变量multi2
。
1.2 函数类型
上面我们说过multi2
和multi3
本质上是一样的,那么作为函数它们是什么类型的?两者的类型实际上都是Int => Int
,前面一个Int代表输入参数类型,后面一个Int代表返回值类型。
1 2 3 4 5 6 7 8 9
| scala> val multi2 = (x: Int) => {x * x} multi2: Int => Int = $$Lambda$1092/594363215@1dd1a777
scala> def multi3 = (x: Int) => {x * x} multi3: Int => Int
scala> val multi4 = (x: Int,name: String) => {name + x * x } multi4: (Int, String) => String = $$Lambda$1093/1039732747@2eb4fe7
|
1.3 一等公民&匿名函数
在Scala中函数是一等公民,这意味着不仅可以定义函数并调用它们,还可以将它们作为值进行传递:
1 2 3 4 5 6 7
| import scala.math.ceil object ScalaApp extends App { val fun = ceil _ println(fun(2.3456))
}
|
在Scala中你不必给每一个函数都命名,如(x: Int) => 3 * x
就是一个匿名函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
| object ScalaApp extends App { (x: Int) => 3 * x val fun = (x: Int) => 3 * x val array01 = Array(1, 2, 3).map((x: Int) => 3 * x) val array02 = Array(1, 2, 3).map(_ * 3) val array03 = Array(1, 2, 3).map(fun) }
|
1.4 特殊的函数表达式
1. 可变长度参数列表
在Java中如果你想要传递可变长度的参数,需要使用String ...args
这种形式,Scala中等效的表达为args: String*
。
1 2 3 4 5 6 7 8 9 10
| object ScalaApp extends App { def echo(args: String*): Unit = { for (arg <- args) println(arg) } echo("spark","hadoop","flink") }
spark hadoop flink
|
2. 传递具名参数
向函数传递参数时候可以指定具体的参数名。
1 2 3 4 5 6 7 8 9 10
| object ScalaApp extends App { def detail(name: String, age: Int): Unit = println(name + ":" + age) detail("myhhub", 12) detail(age = 12, name = "myhhub")
}
|
3. 默认值参数
在定义函数时,可以为参数指定默认值。
1 2 3 4 5 6 7 8 9
| object ScalaApp extends App {
def detail(name: String, age: Int = 88): Unit = println(name + ":" + age)
detail("myhhub") detail("myhhub", 12)
}
|
二、闭包
2.1 闭包的定义
1 2 3
| var more = 10
val addMore = (x: Int) => x + more
|
如上函数addMore
中有两个变量x和more:
- x : 是一个绑定变量(bound variable),因为其是该函数的入参,在函数的上下文中有明确的定义;
- more : 是一个自由变量(free variable),因为函数字面量本生并没有给more赋予任何含义。
按照定义:在创建函数时,如果需要捕获自由变量,那么包含指向被捕获变量的引用的函数就被称为闭包函数。
2.2 修改自由变量
这里需要注意的是,闭包捕获的是变量本身,即是对变量本身的引用,这意味着:
- 闭包外部对自由变量的修改,在闭包内部是可见的;
- 闭包内部对自由变量的修改,在闭包外部也是可见的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| scala> var more = 10 more: Int = 10
scala> val addMore = (x: Int) => {x + more} addMore: Int => Int = $$Lambda$1076/1844473121@876c4f0
scala> addMore(10) res7: Int = 20
scala> more=1000 more: Int = 1000
scala> addMore(10) res8: Int = 1010
|
2.3 自由变量多副本
自由变量可能随着程序的改变而改变,从而产生多个副本,但是闭包永远指向创建时候有效的那个变量副本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| scala> var more = 10 more: Int = 10
scala> val addMore10 = (x: Int) => {x + more} addMore10: Int => Int = $$Lambda$1077/1144251618@1bdaa13c
scala> addMore10(9) res9: Int = 19
scala> var more = 100 more: Int = 100
scala> val addMore100 = (x: Int) => {x + more} addMore100: Int => Int = $$Lambda$1078/626955849@4d0be2ac
scala> addMore100(9) res10: Int = 109
scala> addMore10(9) res11: Int = 19
scala> more res12: Int = 100
|
从上面的示例可以看出重新声明more
后,全局的more
的值是100,但是对于闭包函数addMore10
还是引用的是值为10的more
,这是由虚拟机来实现的,虚拟机会保证more
变量在重新声明后,原来的被捕获的变量副本继续在堆上保持存活。
三、高阶函数
3.1 使用函数作为参数
定义函数时候支持传入函数作为参数,此时新定义的函数被称为高阶函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| object ScalaApp extends App {
def square = (x: Int) => { x * x }
def multi(fun: Int => Int, x: Int) = { fun(x) * 100 }
println(multi(square, 5)) println(multi(_ * 100, 5))
}
|
3.2 函数柯里化
我们上面定义的函数都只支持一个参数列表,而柯里化函数则支持多个参数列表。柯里化指的是将原来接受两个参数的函数变成接受一个参数的函数的过程。新的函数以原有第二个参数作为参数。
1 2 3 4 5
| object ScalaApp extends App { def curriedSum(x: Int)(y: Int) = x + y println(curriedSum(2)(3)) }
|
这里当你调用curriedSum时候,实际上是连着做了两次传统的函数调用,实际执行的柯里化过程如下:
- 第一次调用接收一个名为
x
的Int型参数,返回一个用于第二次调用的函数,假设x
为2,则返回函数2+y
;
- 返回的函数接收参数
y
,并计算并返回值2+3
的值。
想要获得柯里化的中间返回的函数其实也比较简单:
1 2 3 4 5 6 7 8 9
| object ScalaApp extends App { def curriedSum(x: Int)(y: Int) = x + y println(curriedSum(2)(3))
val plus: Int => Int = curriedSum(10)_ println(plus(3)) }
|
柯里化支持多个参数列表,多个参数按照从左到右的顺序依次执行柯里化操作:
1 2 3 4 5 6
| object ScalaApp extends App { def curriedSum(x: Int)(y: Int)(z: String) = x + y + z println(curriedSum(2)(3)("name")) }
|
参考资料
- Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
- 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7