Scala语言学习
函数式编程
什么是函数式编程?
面向对象编程:解决问题时,将问题拆解成一个一个小问题(形成了对象),分别解决
对象关系: 继承,实现,重写, 多态
函数式编程关心的是问题的解决方案(封装功能);重点在于函数(功能)的入参,出参。
函数式编程重要的就是函数
Java中的方法和Scala中函数都可以进行功能的封装,但是方法必须和类型绑定,但是函数不需要。(函数和方法的本质上的区别)
def test(s: String) Unit = {
println(s)
}
- 无参数无返回值
- 无参数有返回值
- 有参数无返回值
- 有参数有返回值
读者自行测试,这里不作详细说明…
函数没有重载的概念
scala中没有throws关键字,所以函数中如果有异常发送,也不需要在函数声明时抛出异常
Scala中可以采用自动推断功能来简化函数的声明(至简原则)
- 如果函数声明时,明确无返回值Unit,那么即使函数体中有return也不起作用
def main(args: Array[String]): Unit = {
def test(): Unit = {
return "zhangsan"
}
println(test())
}
结果:
()
- 如果将函数体的最后一行代码进行返回,那么return关键字可以省略
def main(args: Array[String]): Unit = {
def test(): String={
"zhangsan"
}
println(test())
}
结果:zhangsan
- 如果可以根据函数的最后一行代码推断出类型,那么函数返回值类型可以省略
def main(args: Array[String]): Unit = {
def test() = {
"zhangsan"
}
println(test())
}
结果:zhangsan
- 如果方法体中只有一行代码,那么{ }可以省略
def main(args: Array[String]): Unit = {
//TODO 如果方法体中只有一行代码,那么{} 可以省略
def test() = "zhangsan"
println(test())
}
- 如果函数声明中没有参数列表,小括号可以省略(如果函数声明时小括号()省略了,那么在调用的时候不能增加小括号)
def test = "zhangsan"
println(test)
- 如果明确函数没有返回值,那么等号可以省略,省略后,编译器不会将函数体最后一行代码作为返回值
- 匿名函数
()->{println("XXXXXX")}
函数式编程 - 扩展
可变参数 *
def main(args: Array[String]): Unit = {
//TODO 匿名函数
def test(name : String*): Unit ={
println(name)
}
test("zhangsan", "lisi", "wangwu")
}
ArraySeq(zhangsan, lisi, wangwu)
默认参数
def main(args: Array[String]): Unit = {
//TODO 匿名函数
def test(name:String, age:Int = 20): Unit ={
println(s"${name}--${age}")
}
test("zhangsan")
test("zhangsan", 20)
}
带名参数
test(name="1")
高级函数式编程
Scala 是完全面向函数式编程语言
scala中的函数可以做任何事情
- 函数可以作为函数的返回值
def main(args: Array[String]): Unit = {
def f(): Unit ={
println("XXXXX")
}
def f2()={
f
}
def f3() = {
f _ // TODO _ 声明,当函数作为返回值时,不调用返回的函数
}
f2()
f3()()
}
注意:函数作为返回值时,不加 _ 函数会直接被调用
使用 f _ 时,函数在被返回时,是不会被调用的
- 函数嵌套函数
def main(args: Array[String]): Unit = {
def f1(i: Int) = {
def f2(j : Int): Int={
i*j
}
f2 _
}
println(f1(3)(2))
}
结果:
6
函数柯里化(闭包)
大白话:只要有 函数柯里化, 一定会有闭包。闭包:改变了外部变量的生命周期,把这个变量包含到逻辑的内部,形成了一个闭环的操作叫做闭包
闭包:一个函数在实现逻辑时,将外部的变量引入到函数的内部,改变了这个变量的生命周期,称之为闭包
//TODO 函数柯里化
def f3(i: Int)(j:Int)={
i*j
}
println(f3(3)(2))
结果:6
函数柯里化概念
函数柯里化指的是将原来接收多个参数的函数变成新的接收一个参数的函数的过程,新函数的参数接收原来的第二个参数为唯一参数,如果有n个参数,就是把这个函数分解成n个新函数的过程
- 函数可以作为函数的参数
将函数作为参数传递给另外一个函数时,需要采用特殊的声明方式
()=>Unit:作为函数的类型
参数列表=>返回值类型
def main(args: Array[String]): Unit = {
def f1(f:(Int)=>Int, i: Int): Int ={
f(i) + 5
}
def f2(i:Int):Int={
i*3
}
println(f1(f2, 5))
}
结果:20
f1函数的第一个参数是一个函数
这个函数的形式是:入参是一个Int类型,返回值是一个Int类型
最终这个函数的类型f1:(Int)=>Int
使用匿名函数来改善上述代码:
f1((i:Int)=>{i*3}, 5)
此处的匿名函数,类似于Java中的lambda表达式:
(i:Int)=>{i*3}
- 函数可以赋值给变量
val function1: () => Int = () => {
var temp: Int = 0
for (i <- 1 to 1) {
temp += i
}
temp
}
补充(见识一下Scala中的至简原则吧):
def f4(f:(Int)=>Unit): Unit ={
f(10)
}
f4((i:Int)=>{println(i)}) //TODO 标准形式
f4((i)=>{println(i)}) //TODO 如果将函数作为参数传递时,已经规范了函数入参的类型,传递时可以省略参数的类型
f4((i)=>println(i)) //TODO 如果函数体只有一条语句,{} 可以省略
f4(println(_)) //TODO 如果函数入参数只有一个,,可以省略入参.若参数只使用一次,可以在函数体中用 _ 代替
f4(println) //TODO 可以直接传递一个 入参为一个参数 无返回值形式的函数, 无需加上()
def f5(f:(Int, Int)=>Int): Int ={
f(10, 10)
}
println(f5((i:Int, j:Int)=>{i+j}))
println(f5((i,j)=>{i+j}))
println(f5((i,j)=>i+j))
println(f5(_+_))
递归 和 尾递归
关于递归的语法,和使用读者自行学习,这里不做过多的论述。
注意
- 函数的逻辑代码中调用自身
- 函数在调用自身时,传递的参数应该有规律
- 函数应该有跳出递归的逻辑,否则出现死循环
- 递归函数无法推断函数的返回值类型,所以必须要声明函数的返回值类型
正常的函数递归很容易出现 栈溢出:StackOverflowError异常,scala中可通过尾递归来进行优化。
scala的尾递归优化实际就是优化成了迭代算法。
object 尾递归 {
def main(args: Array[String]): Unit = {
println(foo(10000000))
}
//TODO 普通递归
def foo(n: Long): Long = {
if (n == 1) 1
else n * foo(n - 1)
}
}
结果:
Exception in thread "main" java.lang.StackOverflowError
at o.l.scala.Functions.尾递归$.foo(尾递归.scala:10)
at o.l.scala.Functions.尾递归$.foo(尾递归.scala:10)
at o.l.scala.Functions.尾递归$.foo(尾递归.scala:10)
at o.l.scala.Functions.尾递归$.foo(尾递归.scala:10)
什么是尾递归?
在递归的时候,只有递归,没有任何其他的运算,这就是尾递归。
// 正常的递归
def foo1(n:Long): Long={
if(n==1) throw new RuntimeException
else n * foo1(n-1)
}
foo1(10)
结果:
Exception in thread "main" java.lang.RuntimeException
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:19)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.foo1(尾递归.scala:20)
at o.l.scala.Functions.尾递归$.main(尾递归.scala:7)
at o.l.scala.Functions.尾递归.main(尾递归.scala)
// 进行了尾递归优化
def foo2(n:Long): Long={
if(n==1) throw new RuntimeException
else foo2(n-1)
foo2(100000)
结果:
尾递归优化Exception in thread "main" java.lang.RuntimeException
at o.l.scala.Functions.尾递归$.foo2(尾递归.scala:24)
at o.l.scala.Functions.尾递归$.main(尾递归.scala:10)
at o.l.scala.Functions.尾递归.main(尾递归.scala)
从上述结果可以看出,scala中的尾递归优化只有一次压栈,从而可以避免普通递归容易出现栈溢出的异常。
如果想要使用尾递归来对普通递归函数进行优化,需要使用合适的累加器来实现 。
累加器:不一定只能进行加运算,这里的累加器指的是数据的累加
下面举个例子,通过尾递归来实现 一个数的 阶层
//TODO 使用 累加器 ,通过尾递归 实现阶层的运算
def foo3(n:Long, acc:Long):Long={
if(n==1) acc
else foo3(n-1, n*acc)
}
foo3(5)
结果:
120
过程解析:
foo3(5)
- foo3(5-1=4, 5*1)
- foo3(4-1=3, 541)
- foo3(3-1=2, 543*1)
- foo3(2-1=1, 54321)
- 当n=1的时候,此时的 累加器 acc=5x4x3x2x1, 即为120,这样就通过尾递归得到了优化,避免栈溢出。
过程
将函数的返回类型为Unit的函数称之为过程(procedure),如果明确函数没有返回值,那么等号可以省略。
惰性函数
惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算他们,这带来了一些好处。首先,可以将耗时的计算推迟到绝对需要的时候;其次,您可以创建无限个集合,只要他们继续收到请求,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。
object Scala03_lazy {
def main(args: Array[String]): Unit = {
def sum(n1:Int, n2:Int): Int = {
{
println("======sum()执行了...=====")
n1 + n2
}
}
lazy val res = sum(10, 20)
println("-------------------")
println("res="+res)
}
}
结果:
-------------------
======sum()执行了...=====
res=30
Process finished with exit code 0
注意事项和细节
- lazy不能修饰var类型的变量
- 但是,在调用函数时,加了lazy,会导致函数的执行被推迟,我们在声明一个变量时,如果给声明了lazy,那么变量值的分配也会推迟。比如:lazy val i= 10
Scala中的异常
object Scala04_tryCatch {
def main(args: Array[String]): Unit = {
val a = 10
val b = 0
try {
val result: Int = a / b
}catch {
case ex:ArithmeticException =>println("捕获了除数为0的算术异常")
case ex:Exception => println("捕获了异常")
}finally{
println("====执行finally语句===")
}
}
}
结果:
捕获了除数为0的算术异常
====执行finally语句===
Process finished with exit code 0
object Scala04_tryCatch {
def main(args: Array[String]): Unit = {
val a = 10
val b = 0
try {
val result: Int = a / b
}catch {
case ex:Exception => println("捕获了异常")
case ex:ArithmeticException =>println("捕获了除数为0的算术异常")
}finally{
println("====执行finally语句===")
}
}
}
结果:
捕获了异常
====执行finally语句===
Process finished with exit code 0
Scala中的异常捕获不像Java中的需要注意顺序,Scala中的异常是通过模式匹配来实现的
函数的值传递和名传递
object 值传递与名传递 {
def main(args: Array[String]): Unit = {
foo({
println(".....")
3+4
})
foo2({
println(".....")
3+4
})
}
//TODO 值传递
def foo(a:Int): Unit ={
println("=值传递=")
println(a)
println(a)
println(a)
}
//TODO 名传递
def foo2(a: => Int): Unit={
println("=名传递=")
println(a)
println(a)
println(a)
}
}
结果:
.....
=值传递=
7
7
7
=名传递=
.....
7
.....
7
.....
7
Process finished with exit code 0
从结果可以看出:
-
值传递是将计算后的值传递给函数,这里是将 3+4=7 直接赋值给a,然后再传递给函数;
-
而名传递是将一段逻辑传递给函数(或者说将一段代码传递给函数),这段逻辑最终返回的结果再赋值给 a,随后再将a进行输出
注意,要将名传递与函数传递区别开
//TODO 名传递
def foo2(a: => Int): Unit={
println("=名传递=")
println(a)
println(a)
println(a)
}
//TODO 传递函数
def foo3(a: ()=>Int): Unit ={
println("=函数传递=")
println(a)
println(a())
}
main中: foo2({3+4})
foo3(()=>{3+4})
结果:
=名传递=
7
7
7
=函数传递=
o.l.scala.Functions.值传递与名传递$$$Lambda$4/1205044462@2d6a9952
7
个人理解
名传递 --传递的代码,使用参数名后会直接运行这段逻辑
函数传递 --传递的是一个函数,只有调用这个函数的时候才会执行函数里面的逻辑代码
温馨小贴士:
使用名传递的时候,如果函数的入参只有一个名传递的时候, 调用这个函数时()可以省略,或者{ }可以省略,但是要保证这两者有一个存在;
如果函数的入参有多个名传递的时候, ({ 逻辑代码1 },{ 逻辑代码2 }) , ()是不可以省略的
{ 只有一行代码 } {}可以省略
def foo2(a: => Int): Unit={
println("=名传递=")
println(a)
println(a)
println(a)
}
def foo4(a: =>Int, b: =>Int): Unit ={
println(a)
println(b)
}
// 调用时:
foo2({3+4})
foo2{3+4}
foo2(3+4)
foo4({3+4}, {5+6})
foo4(3+4, 5+6)
使用函数柯里化、名传递、递归实现while循环操作
//TODO 使用函数颗粒化、名传递、递归,实现 while循环
def loop(condition: => Boolean)(op: => Unit): Unit = {
if (condition) {
op
loop(condition)(op)
}
}
var a: Int = 0
loop(a <= 10){
println(a)
a += 1
}
控制抽象
Scala中可以自己定义类似于if-else,while的流程控制语句,即所谓的控制抽象。
有关控制抽象的解释,可参考:大白话 Scala 控制抽象
手撸高阶函数(foreach, filter, map, reduce)
foreach(遍历+行为)
//TODO 手撸 foreach
def foreach(array: Array[Int], op: Int => Unit): Unit = {
for (elem <- array) {
op(elem)
}
}
val array: Array[Int] = Array(1, 3224, 54, 6, 76, 86, 435, 59, 87098, 34, 52, 1)
foreach(array, (i:Int)=>{print(i)})
println()
foreach(array, print) //TODO 简化版
filter(过滤)
def filter(array: Array[Int], op: Int=>Boolean): Array[Int] ={
for (elem <- array if op(elem)) yield elem
}
val filterArray: Array[Int] = filter(array, _ > 1)
foreach(filterArray, println)
结果:
3224
54
6
76
86
435
59
87098
34
52
Process finished with exit code 0
map(映射:一进一出)
def map(array: Array[Int], op: Int=>Int): Array[Int] ={
for (elem <- array) yield op(elem)
}
private val ints: Array[Int] = map(array, x => x * x)
foreach(ints, println)
结果:
1
10394176
2916
36
5776
7396
189225
3481
100
1156
2704
1
Process finished with exit code 0
reduce(聚合)
def reduce(array: Array[Int], op: (Int, Int) => Int): Int = {
var result = array(0)
for (index <- 1 until array.length) {
{
result = op(result, array(index))
}
}
result
}
private val sum: Int = reduce(array, _ * _)
print(sum)