Scala 集合框架:Tuple,Map[K,V],Option[T]
Tuple
Tuple 能让一个方法返回两个或三个值,例如下面的代码输入一个 List[Double],返回元素个数,元素的和及其平方和:
def sumSq(in: List[Double]): (Int, Double, Double) = in.foldLeft((0, 0d, 0d))((t, v) => (t._1 + 1, t._2 + v, t._3 + v * v))
这里,编译器会将在括号中的集合元素看作是一个Tuple,上面的代码会翻译成:Tuple3[Int,Double,Double],这个函数接收两个参数:t和 v,t是一个Tuple3,而v是一个Double。
使用模式匹配可以让上面的代码更易理解:
def sumSq(in: List[Double]) : (Int, Double, Double) =
in.foldLeft((0, 0d, 0d)){
case ((cnt, sum, sq), v) => (cnt + 1, sum + v, sq + v * v)}
当然,也可以用其它方式来创建一个Tuple:
scala> Tuple2(1,2) == Pair(1,2) scala> Pair(1,2) == (1,2) scala> (1,2) == 1 -> 2
其中最后一种写法 1 -> 2 对于传递pairs 是很有用的一种方式。Pairs经常出现在代码中,包括用来创建 Maps 的 name/value 对。
对于Tuple元素的读取,应该使用下标 _n,例如 t._1, t._2 ,t._3 ,注意这里起始为1,并且对元素的引用不能越界。
Map[K,V]
Map 是一个 key/value 对的集合, key 是唯一的,但value 可以不唯一,Scala中的默认Map实现是不可变的,可以用于多线程环境,而且性能与Java中的HashMap相当。
创建一个 Map:
scala> var p = Map(1 -> "David", 9 -> "Elwood") p: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,David), (9,Elwood))
添加一个元素:
scala> p += 8 -> "Archer" scala> p = p + (9 -> "Archer") p: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> David, 8 -> Archer, 9 -> Archer)
如果想取出一个不存在的元素:
scala> p(88) java.util.NoSuchElementException: key not found: 88
这里会抛出异常,虽然看起来并不方便,因为Java的Map能够通过返回null来处理这个情况,但这样做有两个缺点:首先,必须对每个Map的访问进行 null-test,其次,这代表着不能在一个Map中存储null值。对此Scala有一个更好的机制,即Map的get()方法将返回一个包含了結果的Option(Some 或none):
scala> p.get(88) res10: Option[java.lang.String] = None
如果key没有找到,可以返回一个默认值:
scala> p.getOrElse(99, "Nobody") res55: java.lang.String = Nobody scala> p.getOrElse(1, "Nobody") res56: java.lang.String = David
也可以使用flatMap来返回key在1-5之间的值:
scala> 1 to 5 flatMap(p.get) res13: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(David)
注意,表面上来看这里p.get 只是一个方法,但是Scala正期待着一个拥有特定类型的参数,而你传递了一个接收一个参数的方法,这时Scala会将这个方法和它未写的参数提升为一个函数。
删除一个元素:
scala> p -=9 scala> p res24: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,David), (8,Archer), (10,Archer))
同时,我们可以在keys 或values之上进行操作:
//查找最大的key
scala> p.keys.reduceLeft(_ max _)
res22: Int = 10
//查找最大的value
scala> p.values.reduceLeft((a, b) => if (a > b) a else b)
res23: java.lang.String = David
//是否有value包含 "z”
scala> p.values.exists(_.contains("z"))
res28: Boolean = false
//批量增加元素
scala> p ++= List(5 -> "Cat", 6 -> "Dog")
scala> p
res29: scala.collection.immutable.Map[Int,java.lang.String] = Map((5,Cat), (10,Archer), (1,David), (6,Dog), (8,Archer))
//批量减少元素
scala> p --= List(8, 6)
scala> p
res31: scala.collection.immutable.Map[Int,java.lang.String] = Map((5,Cat), (10,Archer), (1,David))
既然Map也是一个集合,这也意味着可以在Map对象上使用map,filter,foldLeft方法。
Option[T]
Option[T]是一个包含零个或一个给定元素类型的集合,它提供了对Java的null的替代,一个Option[T]可以是 Some[T]或者是None,其中None是一个单例对象,在整个Scala应用程序中只有一个实例,但是None也有自己的方法:可以在它之上调用map,flatMap,filter,foreach,所以不管是None或者是Some,都可以使用业务逻辑代码。
// 导入别名是为了不和Scala中的Boolean冲突
scala> import java.lang.{Boolean => JBool}
import java.lang.{Boolean=>JBool}
scala> def tryo[T](f: => T): Option[T] = try {Some(f)} catch {case _ => None}
tryo: [T](f: => T)Option[T]
scala> def toInt(s: String): Option[Int] = tryo(s.toInt)
toInt: (s: String)Option[Int]
scala> def toBool(s: String) = tryo(JBool.parseBoolean(s))
toBool: (s: String)Option[Boolean]
有了上面的代码,可以不再使用 null-test:
def personFromParams(p: Map[String, String]): Option[Person] =
for {name <- p.get("name")
ageStr <- p.get("age")
age <- toInt(ageStr)
validStr <- p.get("valid")
valid <- toBool(validStr)}
yield new Person(name, age, valid)
可以使用get方法来取得Option中的值:
scala> Some(3).get res32: Int = 3
但是对None调用get方法会抛出一个异常:
scala> None.get java.util.NoSuchElementException: None.get
像map一样,Option也有一个getOrElse方法,在内容未定义时返回一个默认值:
scala> Some(3).getOrElse(44) res34: Int = 3 scala> None.getOrElse(44) res35: Int = 44
Option 的出现,减少了很多复杂的代码,让业务逻辑看起来更加清晰自然。
Related posts:
评论