1. 多参数列表

Scala 允许你指明函数的最后一个参数可以是重复的。这可以允许客户向函数传入可变长度参数列表。想要标注一个重复参数,在参数的类型之后加一个 * 。例如:

def echo(args: String*) = {
  for (arg <- args) println(arg)
}

这样定义, echo 可以被零个至多个 String 参数调用

echo()
echo("one")
echo("hello", "world")

函数内部,重复参数的类型是声明参数类型的数组。因此, echo 函数里被声明为类型“ String* ”
的 args 的类型实际上是 Array[String] 。然而,如果你有一个合适类型的数组,并尝试把它当作
重复参数传入,你会得到一个编译器错误:

val arr = Array("I`m", "from", "China")
echo(arr)

# 编译报错
error: type mismatch;

found : Array[java.lang.String]
required: String

要实现这个做法,你需要在数组参数后添加 : _* 符号,像这样:

val arr = Array("I`m", "from", "China")
echo(arr: _*)

这个标注 : _* 告诉编译器把 arr 的每个元素当作参数,而不是当作单一的参数传给 echo 。

2. Tuple2与Map

Map(1->"a", 2->"b").map { case (i, j)=> 
  (1, j)
}

res0: scala.collection.immutable.Map[Int,String] = Map(1 -> b)

如果map中返回的是Tuple2类型, 那么最后结果会转化成Map, 同时相同的key会被覆盖掉

Map(1->"a", 2->"b").map { case (i, j)=> 
  (1, 1, j)
}

res3: scala.collection.immutable.Iterable[(Int, Int, String)] = List((1,1,a), (1,1,b))

如果map中返回的不是Tuple2类型, 那么最后结果就是List而不是Map, 此时就不必担心数据被覆盖的问题了

3. Java集合与Scala集合相互转换

scala.collection.convert 包下定义了常用的一些隐式转换类

import java.util

import scala.collection.convert.ImplicitConversionsToScala._
import scala.collection.convert.ImplicitConversionsToJava._

object CommonImplicit {

    implicit def map2ScalaMap[K, V](map: util.Map[K, V]): Map[K, V] = `map AsScala`(map).toMap
    implicit def list2ScalaList[T](list: util.List[T]): List[T] = `list asScalaBuffer`(list).toList

    implicit def map2JavaMap[K, V](map: Map[K, V]): util.Map[K, V] = `map AsJavaMap`(map)
    implicit def map2JavaList[T](list: List[T]): util.List[T] = `seq AsJavaList`(list)
}

在使用时直接 import CommonImplicit._ 即可

4. reduce与foldLeft

reduce: 对集合元素进行归约操作, 默认从左到右两两运算, 然后结果与右边的元素继续运算, 最终得到结果

scala> List[Int]().reduce(_ + _)

scala> java.lang.UnsupportedOperationException: empty.reduceLeft
  at scala.collection.LinearSeqOptimized.reduceLeft(LinearSeqOptimized.scala:139)

查看源码发现reduce默认调用reduceLeft, 而reduceLeft会判断集合是否为空, 如果是则抛出异常

clipboard.png

上图我们可以看到reduceLeft实际调用的是foldLeft, 所以这时候需要用foldLeft来代替reduceLeft
因为集合为空, 所以我们需要给一个初始的head填充到原来的集合中:

scala> List[Int]().foldLeft(0)(_ + _)
等价于
scala> List[Int](0).reduce(_ + _)

5. map合并, k相同v累加

使用 /:

(map1 /: map2) { case (map, (k, v)) => map + (k -> v + map.getOrElse(k, 0))

查看源码

image.png

其实是以其中一个map作为基础, 将另一个map的键值对做折叠累加操作

6. Future

Future[T] 是一个容器类型,代表一种返回值类型为 T 的计算。 计算可能会出错,也可能会超时.

object Future {
  def apply[T](body: => T)(implicit execctx: ExecutionContext): Future[T]
}

要异步执行的计算通过传名参数 body 传入。 第二个参数是一个隐式参数,隐式参数是说,函数调用时,如果作用域中存在一个匹配的隐式值,就无需显示指定这个参数。ExecutionContext 可以执行一个 Future,可以把它看作是一个线程池,是绝大部分 Future API 的隐式参数。

import scala.concurrent.ExecutionContext.Implicits.global
语句引入了一个全局的执行上下文,确保了隐式值的存在。 这时候,只需要一个单元素列表,可以用大括号来代替小括号。

计算会在 Future 创建后的某个不确定时间点上由ExecutionContext给其分配的某个线程中执行。

Future回调

Future {
    // do something ...
    ret
}.onComplete {
   case Success(ret) => println(s"计算成功返回 $ret")
   case Failure(ex) => println("计算失败, 异常: $ex")
}

也可以单独使用 onSuccessonFailure 分别注册成功/失败回调

Future组合

所有容器类型都可以进行mapflatMap操作,也可以用在 for 语句中。 作为一种容器类型,Future 支持这些操作也不足为奇!

  1. map

    def cal(i: Int, j: Int): Future[Int] = Future(i+j)
    val b: Future[Boolean] = cal(1, 2).map(_ > 3)
  2. flatMap

    def check(i: Int): Future[Boolean] = Future(i == 5)
    
    val b: Future[Boolean] = cal(1, 3).flatMap(i => check(i))
  3. for
    上面的flatMap,也可以写成 for 语句

    for {
        i <- cal(1, 4)
        b <- check(i)
    } yield b
    
    val b: Future[Boolean] = a.flatMap(s => check(s))

    注意 上述 forflatMap 的两种写法, future的创建存在先后顺序, 并且后续的future依赖前面future的执行结果, 所以如果存在并行执行的future, 需要事先在 for 之外创建 Future.

    val one = cal(1, 5)
    val two = cal(2, 7)
    val three = cal(3, 9)
    
    for {
        o <- one
        d <- two
        t <- three
        flag <- check(o + d + t)
    } yield flag

    for 语句之前,三个 Future 在创建之后就开始各自独立的运行。 而 check 是在前面3个future执行完毕后才开始执行.

执行上下文ExecutionContext

到现在为止,我们都是使用隐式可用的全局 ExecutionContext 来执行这些代码块。 通常,更好的方式是创建一个专用的 ExecutionContext。 可以从 Java的 ExecutorService 来它,这也意味着,可以异步的调整线程池来执行数据库调用,应用的其他部分不受影响。

val executorService = Executors.newFixedThreadPool(4)
implicit val executionContext = ExecutionContext.fromExecutorService(executorService)

最好有一些专属的 ExecutionContext 来处理不同场景的计算。怎样调整这些线程池大小取决于应用的特征.

参考: 类型 Future


加林
175 声望3 粉丝

极度渴望成功 愿付非凡代价


引用和评论

0 条评论