这篇讲Scala中的数据容器,掌握这些容器的使用就基本可以上手Scala了,之后的高级功能在实际的项目中体会。
1.元组(Tuples)
元组是一个容器,用于存放两个或多个不同类型的元素。它是不可变的。它自从创建之后就能修改了。它的语法简单,如下所示。
val twoElement = ("10",true)
val threeElemment = ("10","harry",true)
当你想要把一些不相关的元素聚合在一起时,元组就派上用场了。
- 当所有元素都是同一类型时,可以使用集合,比如数组或列表。当元素是不同的类型但是之间有联系时,可以使用类,把它们当成类字段来存储。
- 但是在某些场景下使用类没有必要。比如你想要一个有多个返回值的函数,此时元组比类更合适。
元组的下标从1开始。下面这个例子展示怎么访问元组中的元素。
private val value: String = threeElemment._1
2.Option类型
Option是一种数据类型,用来表示值是可选的,即要么无值要么有值。它要么是样本类Some的实例,要么是单例对象None的实例。Some类的实例可以存储任何类型的数据,用来表示有值。None对象表示无值。
Option类型可以在函数或方法中作为值返回。返回Some(x)表示有值,x是真正的返回值。返回None表示无值。从函数返回的Option类型对象可以用于模式匹配中。
object Test {
def main(args: Array[String]): Unit = {
val code = colorCode("orange")
code match {
case Some(c) => println("code for orange is :" +c)
case None => println("code not defined for orange")
}
}
def colorCode(color:String): Option[Int] ={
color match {
case "red" => Some(1)
case "blue" => Some(2)
case "green" => Some(3)
case _ =>None
}
}
}
使用Option类型有助于避免空指针异常。在很多语言中,NULL用于表示无值。以C++、Java中一个返回整数的函数为例,如果对于给定的参数没有合法的整数可以返回,函数可能返回NULL。调用者如果没有检查返回值是否为NULL,而直接使用,就可能导致程序崩溃。在Scala中,由于有了严格类型检查和Option类型,这样的错误得以避免。
3.集合
集合是一种容器类的数据结构,可以容纳零个或多个元素。它是一种抽象的数据结构。它支持声明式编程。它有方便使用的接口,使用这些接口就不必手动遍历所有的元素了。
Scala有丰富的集合类,集合类包含各种类型。所有的集合类都有同样的接口。Scala的集合类可以分为三类:序列、集合、map。
1.序列(Seq)
序列表示有先后次序的元素集合。由于元素是有次序的,因此可以根据位置来访问集合中的元素。序列是类可迭代集合的特例Iterable
。与迭代不同,序列总是具有定义的元素顺序。序列提供了一种apply
索引,指数的范围从0一直到length一个序列,序列支持多种方法来找到子序列。
case class Person(name: String)
object Test {
val nums = Seq(1,2,3)
def main(args: Array[String]): Unit = {
val people=Seq(
Person("zhoujian"),
Person("Jerry"),
Person("MJ"))
val x = people.apply(1)
print(x.name)
}
}
1.1在Scala中Seq与List的区别?
Difference between a Seq and a List in Scala
1.2 How to add (append and prepend) elements to a Seq
Because Seq
is immutable, you can’t add elements to an existing Seq
. The way you work with Seq
is to modify the elements it contains as you assign the results to a new Seq
.
Method | Description | Example |
---|---|---|
:+ |
append 1 item | oldSeq :+ e |
++ |
append N items | oldSeq ++ newSeq |
+: |
prepend 1 item | e +: oldSeq |
++: |
prepend N items | newSeq ++: oldSeq |
Append and prepend examples
These examples show how to use the append and prepend methods:
val v1 = Seq(4,5,6) # List(4, 5, 6)
val v2 = v1 :+ 7 # List(4, 5, 6, 7)
val v3 = v2 ++ Seq(8,9) # List(4, 5, 6, 7, 8, 9)
val v4 = 3 +: v3 # List(3, 4, 5, 6, 7, 8, 9)
val v5 = Seq(1,2) ++: v4 # List(1, 2, 3, 4, 5, 6, 7, 8, 9)
2.数组(Array)
数组是一个有索引的元素序列。数组中的所有元素都是相同类型的。它是可变的数据结构。可以修改数组中的元素,但是你不能在它创建之后增加元素。它是定长的。
Scala数据类似其他语言中的数组。访问其中的任意一个元素都占用固定的时间。数组的索引从0开始。要访问元素或修改元素,可以通过索引值达成,索引值位于括号内。下面是一个例子。
//初始化数组
var z = new Array[String](3)
//或者
private val array = Array(10,20,30,40)
array(0) = 50
val first = array(0)
关于数组的基本操作如下:
- 通过索引值访问元素
- 通过索引值修改元素
3.列表(List)
列表是一个线性的元素列表,其中存放一堆相同类型的元素。它是一种递归的结构,而不像数组(扁平的数据结构)。和数组不同,它是可变的,创建后即不可修改。列表是Scala和其他函数式语言中最常用的一种数据结构。
尽管可以根据索引来访问列表中的元素,但这并不高效。访问时间和元素在列表中的位置成正比。
下面的代码展示了几种创建列表的方式。
private val xs = List(10,20,30,40)
private val ys: List[Int] = (1 to 100).toList
关于列表的基本操作如下:
- 访问第一个元素。为此,List类提供了一个叫head的方法。
- 访问第一个元素之后的所有元素。为此,List类提供了一个叫做tail的方法。
- 判断列表是否为空。为此,List提供了一个叫做isEmpty的方法,放列表为空时,它返回true。
4.向量(Vector)
向量是一个结合了列表和数组各自特性的类。它拥有数组和列表各自的性能优点。根据索引访问元素占用固定的时间,线性访问元素也占用固定的时间。向量支持快速修改和访问任意位置的元素。
下面是一个例子:
//定义一个向量
private val vector = Vector(0,10,20,30,40)
//往向量里面添加值
val v1 = vector:+50
val v2 = vector:+60
val v3 = vector(3)
5.集合(Set)
set
是一个无序的集合,其中的每一个元素都不同。它没有重复的元素,而且,也没法通过索引来访问元素,因为它没有索引。
下面是一个例子:
//创建一个集合
private val set = Set("apple","orange","pear","banana")
集合支持两种基本操作。
-
contains
:如果当前集合包含这个元素,则返回true。元素作为参数传递进来。 -
isEmpty
:如果当前集合为空,则返回true。
6.map(Map)
map
是一个键值对集合。在其他语言中,它也叫做字典、关联数组或者hash map
。它是一个高效的数据结构,适合根据键找对应的值。千万不要把它和Hadoop MapReduce
中的map
混淆了。Hadoop MapReduce
中的map
指在集合的一种操作。
下面这段代码展示了如何创建并使用map
。
//创建一个集合
private val map = Map("USA" -> "Washington D.C","UK"->"London","India"->"New Delhi")
val indiaCapital = map("India")
Scala还有其他集合类,这里就不一一介绍了。然而,只要掌握了以上内容,这些就足以高效的使用Scala了。
4.集合类的高级方法
Scala集合的强大之处就是在于这些高阶方法。这些高阶方法把函数当成参数。需要注意的是,这些高阶方法并没有改变集合。下面介绍一些常用的高阶方法。例子中使用的是List集合,但是所有的Scala集合类都支持这些高阶方法。
1.map
map方法的参数是一个函数,它将这个函数作用域集合中的每一个元素,返回由其返回值所组成的集合。这个返回的集合中的元素个数和集合中的个数一致。然而,返回集合中的元素类型有可能不一样。下面是一个例子:
object Test {
def main(args: Array[String]): Unit = {
val xs = List(1,2,3,4)
val ys :List[Double] = xs.map((x:Int) =>x*20.0)
print(ys)
}
}
在上面的例子中,需要注意的是,xs
的类型是List[Int]
,而ys
的类型是List[Double]
。
如果一个函数只有一个参数,那么包裹参数列表的圆括号可以用大括号代替的。下面的两条语句是等价的。
private val ys1: List[Double] = xs.map((x:Int) => x*20.0)
private val ys2 = xs.map{(x:Int) => x*20.0}
就想之前说的一样,Scala允许以操作符的方式调用任何方法,为了进一步提高代码的可读性。
2.flatMap
Scala集合的flatMap
方法类似与map
,它的参数是一个函数,它把这个函数作用于集合中的每一个元素,返回另外一个集合。这个函数作用于原函数中一个元素之后会返回一个集合。这样,最后就会得到一个元素都是集合的集合。使用map方法,就是这样的结果。但是使用flatMap
会得到一个Seq[Char]
的集合。下面是一个使用flatMap
的例子,试图比较这两种不同的作用:
scala> val fruits = Seq("apple", "banana", "orange")
fruits: Seq[String] = List(apple, banana, orange)
scala> fruits.map(_.toUpperCase)
res0: Seq[String] = List(APPLE, BANANA, ORANGE)
scala> fruits.flatMap(_.toUpperCase)
res1: Seq[Char] = List(A, P, P, L, E, B, A, N, A, N, A, O, R, A, N, G, E)
利用flatten
方法将map
形式转化为flatmap
形式。
3.filter(过滤)
filter
方法将创建谓词函数作用域集合中的每一个元素,返回另一个集合,其中只包含计算结果为真的元素。谓词函数指的是返回一个布尔值的函数。它要么返回true,要么返回false。
private val list: List[Int] = (1 to 100).toList
private val intToBoolean: Int => Boolean = _ % 2 == 0
list filter intToBoolean
4.foreach
forech
方法的参数是一个函数,它把这个函数作用于集合中的每一个元素,但是不返回任何东西。它和map类似,唯一区别在于map返回一个集合,而foreach
不返回任何东西。由于它的无返回值特性它很少使用。
private val word: Array[String] = "Scala is fun ".split(" ")
word.foreach(println)
5.reduce
`reduce`方法返回一个值。顾名思义,**它将一个集合整合成一个值**。它的参数是一个函数,这个函数有两个参数,并返回一个值。从本质上说,这个函数是一个二元操作符,并且满足结合律和交换律。
private val ints = List(2,4,8,10)
private val sum: Int = ints reduce{ (x, y) => x+y}
private val product: Int = ints reduce{ (x, y) => x*y}
private val max: Int = ints reduce{ (x, y) => if (x > y) x else y}
private val min: Int = ints reduce{ (x, y) => if (x < y) x else y}
需要注意的是,Hadoop MapReduce中的map/reduce和我们上面说的map/reduce是相似的。
参考文献
https://docs.scala-lang.org/zh-cn/overviews/
https://alvinalexander.com/scala/seq-class-methods-examples-syntax