Scala函数式编程(三) scala集合和函数(Scalar函数)

网友投稿 379 2022-06-21


前面已经稍微介绍了scala的常用语法以及面向对象的一些简要知识,这次是补充上一章的,主要会介绍集合和函数。

注意噢,函数和方法是不一样的,方法是在类里面定义的,函数是可以单独存在的(严格来说,在scala内部,每个函数都是一个类)

一.scala集合介绍

还记得上一章介绍的object的apply方法吗,很多数据结构其实都用到了它,从而让我们可以直接用List(...)这样来新建一个List,而不用自己手动new一个。

PS:注意,scala默认的数据结构都是不可变的,就是说一个List,没法删除或增加新的元素。当然,也有“可变”的数据结构,后面会介绍到。

1.1 List

得益于apply方法,我们可以不通过new来新建数据结构。

//通过工厂,新建一个List,这个List是不可变的

scala> val numbers = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)

numbers: List[Int] = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)

1.2 元组Tuple

Tuple这个概念在python应用比较广泛,它可以将多种数据类型(Int,String,Double等)打包在一起

scala> val tup = (1,1,2.1,"tuple",'c') //将多种不同数据结构打包一起,可以有重复

tup: (Int, Int, Double, String, Char) = (1,1,2.1,tuple,c)

但在scala中,Tuple是有长度限制的,那就是一个Tuple最多只能有22个元素。

scala> val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)

tup: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)

//一个Tuple超过22个元素,报错了

scala> val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)

:1: error: too many elements for tuple: 23, allowed: 22

val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)

可以看到上面,一但一个Tuple超过22个元素,就会报错了。至于为什么是22这个神奇的数字,好像一直没有一个统一的论调。有人开玩笑的说23才对,因为有部电影的名字叫《The Number 23》~~

1.3 Map和Option

在说Map之前,需要先介绍Option,因为Map里面存的就是Option,这个后面介绍。

Option翻译过来是选项,本质上,它也确实是一个选项,用来告诉你存不存在。还是用实例说明吧:

//Option里面可以存普通数据类型

scala> val optionInt = Option(1)

optionInt: Option[Int] = Some(1)

scala> optionInt.get

res8: Int = 1

//但一个Option也可能为空

scala> val optionNone = Option(null)

optionNone: Option[Null] = None

//当Option里面是空的时候,是get不出东西的,还会报错

scala> optionNone.get

java.util.NoSuchElementException: None.get

at scala.None$.get(Option.scala:347)

at scala.None$.get(Option.scala:345)

... 32 elided

//这个时候可以判断它就是空的

scala> optionNone.isEmpty

res11: Boolean = true

//但可以用getOrElse()方法,当Option里面有东西的时候,就返回那个东西,如果没有东西,就返回getOrElse()的参数的内容

scala> optionNone.getOrElse("this is null") //里面没东西

res12: String = this is null

scala> optionInt.getOrElse("this is null") //里面有东西

res15: Any = 1

再说Map,Map里面的value的类型并不是你赋的那个数据类型,而是Option。即Map(key -> Option,key1 -> Option)。

scala> val map = Map("test1" -> 1,"test2" -> 2)

map: scala.collection.immutable.Map[String,Int] = Map(test1 -> 1, test2 -> 2)

scala> map.get("test1")

res16: Option[Int] = Some(1)

scala> map.get("test3")

res17: Option[Int] = None

scala> map.get("test3").getOrElse("this is null")

res18: Any = this is null

这样的好处是什么呢?还记得在java里面,每次都得为java为空的情况做判断的痛苦吗。在scala,这些烦恼通通不存在。有了Option,妈妈再也不用担心NullPointerException啦。

1.4 常用函数组合子

匿名函数

将集合的函数组合子,那肯定得先将匿名函数。如果有用过python中的lambda表达式,那应该就很了解这种方式了。

前面说到,在scala中,函数就是对象,匿名函数也是函数。举个简单的例子:

//创建一个匿名函数

scala> val addOne = (x: Int) => x + 1

addOne: (Int) => Int =

scala> addOne(1)

res4: Int = 2

注意,函数里面是可以不用return的,最后面的那个x+1就是匿名函数的返回值了。

map,reduce

因为hadoop的出现,MapReduce都被说烂了。虽然Hadoop的Mapreduce起源自函数式的map和reduce,但两者其实是不一样的。感兴趣的可以看看我之前写过的一篇:从分治算法到 Hadoop MapReduce

函数式里面的map呢,粗略的说,其实相当一个有返回值的for循环。

scala> val list = List(1,2,3)

list: List[Int] = List(1, 2, 3)

scala> list.map(_ + 1) //这里的(_+1)其实就是一个匿名函数 //让List中每一个元素+1,并返回

res29: List[Int] = List(2, 3, 4)

至于reduce呢,也是像for循环,只是不像map每次循环是当前元素,reduce除了当前元素,还有上一次循环的结果,还是看看例子吧:

scala> list.reduce((i,j) => i + j) //两个两个一起循环,这里是让两个相加

res28: Int = 6

比如上面的例子,有1,2,3三个数。第一次循环的两个是1,2,1+2就等于3,第二次循环就是上次的结果3和原本的3,3+3等于6,结果就是6。

filter

filter故名思意,就是过滤的意思,可以在filter中传进去一个匿名函数,返回布尔值。返回true的则保留,返回false的丢弃。

scala> val numbers = List(1, 2, 3, 4)

numbers: List[Int] = List(1, 2, 3, 4)

//过滤出余2等于0的

scala> numbers.filter((i: Int) => i % 2 == 0)

res0: List[Int] = List(2, 4)

foldLeft

这个和reduce类似,也是遍历,除了当前元素,还有上一次迭代的结果。区别在于foldLeft有一个初始值。

scala> val numbers = List(1, 2, 3, 4)

numbers: List[Int] = List(1, 2, 3, 4)

//(m: Int, n: Int) => m + n这部分是一个匿名函数

scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n)

res30: Int = 10

二.scala函数

在最前面有介绍到,函数就是对象。那为什么函数能直接运行呢?这其实得益于object的apply这个语法糖。

偏函数

偏函数(PartialFunction),从某种意义上来说,偏函数也是scala中挺重要的一个语法糖。

偏函数它长这样:

PartialFunction[A, B] //接收一个A类型的参数,返回B类型的参数

值得一提的是,scala中有一个关键字case,就是使用偏函数。继续举例子:

//下面的case就是一个偏函数PartialFunction[Int, String]

scala> val one: PartialFunction[Int, String] = { case 1 => "one" }

one: PartialFunction[Int,String] =

scala> one(1)

res11: String = Int

scala> one("one")

:13: error: type mismatch;

found : String("one")

required: Int

one("one")

case关键字会匹配符合条件的类型或值,如果不符合,会报错。内部实现就不介绍了,知道是个常用语法糖就好。

而这个case关键字也正是实现scala模式匹配中,必不可少的一环,有兴趣的童鞋可以看看我这篇:scala模式匹配详细解析

这里再说一个常见应用吧,函数组合子中有一个collect,它需要的参数就是一个偏函数。下面看个有意思的例子:

//这个list里面的Any类型

scala> val list:List[Any] = List(1, 3, 5, "seven")

list: List[Any] = List(1, 3, 5, seven)

//使用map会报错,因为map接收的参数是普通函

scala> list.map { case i: Int => i + 1 }

scala.MatchError: seven (of class java.lang.String)

at $anonfun$1.apply(:13)

at $anonfun$1.apply(:13)

at scala.collection.immutable.List.map(List.scala:277)

... 32 elided

//但如果用collect函数就可以,因为collect接收的参数是偏函数,它会自动使用偏函数的一些特性,所以可以自动过滤掉不符合的数据类型

scala> list.collect { case i: Int => i + 1 }

res15: List[Int] = List(2, 4, 6)

因为collect接收的参数是偏函数,它会自动使用偏函数的特性,自动过滤不符合的数据类型,而map就做不到。

部分应用函数

所谓部分应用的意思,就是说,当调用一个函数时,可以仅传递一部分参数。而这样会生成一个新的函数,来个实例看看吧:

//定义一个打印两个输出参数的函数

scala> def partial(i:Int,j:Int) : Unit = {

| println(i)

| println(j)

| }

partial: (i: Int,j: Int)Unit

//赋一个值给上面那个函数,另一个参数不赋值,生成一个新的函数

scala> val partialFun = partial(5,_:Int)

partialFun: Int => Unit =

//只要一个参数就可以调用啦

scala> partialFun(10)

5

10

部分应用函数,主要是作用是代码复用,同时也能够增加一定的代码可读性。

当然还有更多有意思的用法,后面有机会说到再说。

函数柯里化

刚开始,听到柯里化的时候很奇怪。柯里?啥玩意?

后来才知道,其实柯里是从curry音译过来的,这个是个人名,就是发明了柯里化的发明人。

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

看看具体是怎么使用吧:

//我们知道函数可以这样定义,让它接收两个参数

scala> def curring(i:Int)(j: Int): Boolean = {false}

curring: (i: Int)(j: Int)Boolean

//可以把这个函数赋值给一个变量,注意变量的类型

scala> val curringVal:(Int => (Int => Boolean)) = curring _

curringVal: Int => (Int => Boolean) =

//可以让这个变量接收一个参数,又变成另一个函数了

scala> val curringVal_1 = curringVal(5)

curringVal_1: Int => Boolean =

//再用这个变量接收一个参数,终于能返回结果了

scala> curringVal_1(10)

res32: Boolean = false

柯里化其实是把一个函数变成一个调用链的过程,和上面的部分应用函数看起来有点像。

这几个部分初次看可能不知道它究竟有什么用,其实这些功能的一个主要用途是函数式的依赖注入。通过这部分技术可以把被依赖的函数以参数的形式传递给上层函数。限于篇幅这里就先省略,后面再介绍。

结语:

此次介绍的是scala集合的一些内容,以及一些函数的特性,再说一遍,函数其实就是对象。

我一直有一种观点,在学习新的东西的时候,一些偏固定规则的东西,比如语法。不用全部记熟,只要知道大概原理,有个映像就行。

比如说scala的函数式编程,或是java的OOP,不需要抱有先把语法学完,再学习相关的编程理念,这在我看来是有点本末倒置了。

我一般的做法,是先熟悉大概的语法,然后去学习语言的精髓。当碰到不懂的时候,再反过来查询具体的语法,有了目标之后,语法反而变得不是那么枯燥了。

以上只是我的一些个人看法,那么本篇到此就结束了。

以上~~


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java线程池中线程的生命周期(线程池线程存活时间)
下一篇:Scala函数式编程(四)函数式的数据结构 上(Scalar函数)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~