Scala的一些語法技巧

1. 多參數列表

Scala 容許你指明函數的最後一個參數能夠是重複的。這能夠容許客戶向函數傳入可變長度參數列表。想要標註一個重複參數,在參數的類型以後加一個 * 。例如:html

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

這樣定義, echo 能夠被零個至多個 String 參數調用java

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

函數內部,重複參數的類型是聲明參數類型的數組。所以, echo 函數裏被聲明爲類型「 String* 」
的 args 的類型其實是 Array[String] 。然而,若是你有一個合適類型的數組,並嘗試把它看成
重複參數傳入,你會獲得一個編譯器錯誤:git

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會被覆蓋掉app

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 包下定義了經常使用的一些隱式轉換類ide

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: 對集合元素進行歸約操做, 默認從左到右兩兩運算, 而後結果與右邊的元素繼續運算, 最終獲得結果ui

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).map { 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

相關文章
相關標籤/搜索