挑逗 Java 程序員的那些 Scala 絕技

有個(gè)問(wèn)題一直困擾著 Scala 社區(qū)盅藻,為什么一些 Java 開(kāi)發(fā)者將 Scala 捧到了天上霍骄,認(rèn)為它是來(lái)自上帝之吻的完美語(yǔ)言涝缝;而另外一些 Java 開(kāi)發(fā)者卻對(duì)它望而卻步扑庞,認(rèn)為它過(guò)于復(fù)雜而難以理解。同樣是 Java 開(kāi)發(fā)者拒逮,為何會(huì)出現(xiàn)兩種截然不同的態(tài)度罐氨,我想這其中一定有誤會(huì)。Scala 是一粒金子滩援,但是被一些表面上看起來(lái)非常復(fù)雜的概念或語(yǔ)法包裹的太嚴(yán)實(shí)岂昭,以至于人們很難在短時(shí)間內(nèi)搞清楚它的價(jià)值。與此同時(shí)狠怨,Java 也在不斷地摸索前進(jìn)约啊,但是由于 Java 背負(fù)了沉重的歷史包袱,所以每向前一步都顯得異常艱難佣赖。本文主要面向 Java 開(kāi)發(fā)人員恰矩,希望從解決 Java 中實(shí)際存在的問(wèn)題出發(fā),梳理最容易吸引 Java 開(kāi)發(fā)者的一些 Scala 特性憎蛤。希望可以幫助大家快速找到那些真正可以打動(dòng)你的點(diǎn)外傅。

類(lèi)型推斷

挑逗指數(shù): 四星

我們知道纪吮,Scala 一向以強(qiáng)大的類(lèi)型推斷聞名于世。很多時(shí)候萎胰,我們無(wú)須關(guān)心 Scala 類(lèi)型推斷系統(tǒng)的存在碾盟,因?yàn)楹芏鄷r(shí)候它推斷的結(jié)果跟直覺(jué)是一致的。 Java 在 2016 年也新增了一份提議JEP 286技竟,計(jì)劃為 Java 10 引入局部變量類(lèi)型推斷(Local-Variable Type Inference)冰肴。利用這個(gè)特性,我們可以使用 var 定義變量而無(wú)需顯式聲明其類(lèi)型榔组。很多人認(rèn)為這是一項(xiàng)激動(dòng)人心的特性熙尉,但是高興之前我們要先看看它會(huì)為我們帶來(lái)哪些問(wèn)題。

與 Java 7 的鉆石操作符沖突

Java 7 引進(jìn)了鉆石操作符搓扯,使得我們可以降低表達(dá)式右側(cè)的冗余類(lèi)型信息检痰,例如:

List<Integer> numbers = new ArrayList<>();

如果引入了 var,則會(huì)導(dǎo)致左側(cè)的類(lèi)型丟失锨推,從而導(dǎo)致整個(gè)表達(dá)式的類(lèi)型丟失:

val numbers = new ArrayList<>();

所以 var 和 鉆石操作符必須二選一铅歼,魚(yú)與熊掌不可兼得。

容易導(dǎo)致錯(cuò)誤的代碼

下面是一段檢查用戶(hù)是否存在的 Java 代碼:

public boolean userExistsIn(Set<Long> userIds) {
    var userId = getCurrentUserId();
    return userIds.contains(userId);
}

請(qǐng)仔細(xì)觀察上述代碼换可,你能一眼看出問(wèn)題所在嗎谭贪?
userId 的類(lèi)型被 var 隱去了,如果 getCurrentUserId() 返回的是 String 類(lèi)型锦担,上述代碼仍然可以正常通過(guò)編譯,卻無(wú)形中埋下了隱患慨削,這個(gè)方法將會(huì)永遠(yuǎn)返回 false洞渔, 因?yàn)?Set<Long>.contains 方法接受的參數(shù)類(lèi)型是 Object「刻可能有人會(huì)說(shuō)磁椒,就算顯式聲明了類(lèi)型,不也是于事無(wú)補(bǔ)嗎玫芦?

public boolean userExistsIn(Set<Long> userIds) {
    String userId = getCurrentUserId();
    return userIds.contains(userId);
}

Java 的優(yōu)勢(shì)在于它的類(lèi)型可讀性浆熔,如果顯式聲明了 userId 的類(lèi)型,雖然還是可以正常通過(guò)編譯桥帆,但是在代碼審查時(shí)医增,這個(gè)錯(cuò)誤將會(huì)更容易被發(fā)現(xiàn)。
這種類(lèi)型的錯(cuò)誤在 Java 中非常容易發(fā)生老虫,因?yàn)?getCurrentUserId() 方法很可能因?yàn)橹貥?gòu)而改變了返回類(lèi)型叶骨,而 Java 編譯器卻在關(guān)鍵時(shí)刻背叛了你,沒(méi)有報(bào)告任何的編譯錯(cuò)誤祈匙。
雖然這是由于 Java 的歷史原因?qū)е碌暮龉簦怯捎?var 的引入天揖,會(huì)導(dǎo)致這個(gè)錯(cuò)誤不斷的蔓延。

很顯然跪帝,在 Scala 中今膊,這種低級(jí)錯(cuò)誤是無(wú)法逃過(guò)編譯器法眼的:

def userExistsIn(userIds: Set[Long]): Boolean = {
    val userId = getCurrentUserId()
    userIds.contains(userId)
}

如果 userId 不是 Long 類(lèi)型,則上面的程序無(wú)法通過(guò)編譯伞剑。

字符串增強(qiáng)

挑逗指數(shù): 四星

常用操作

Scala 針對(duì)字符作進(jìn)行了增強(qiáng)斑唬,提供了更多的使用操作:

//字符串去重
"aabbcc".distinct // "abc"

//取前n個(gè)字符,如果n大于字符串長(zhǎng)度返回原字符串
"abcd".take(10) // "abcd"

//字符串排序
"bcad".sorted // "abcd"

//過(guò)濾特定字符
"bcad".filter(_ != 'a') // "bcd"

//類(lèi)型轉(zhuǎn)換
"true".toBoolean
"123".toInt
"123.0".toDouble

其實(shí)你完全可以把 String 當(dāng)做 Seq[Char] 使用纸泄,利用 Scala 強(qiáng)大的集合操作赖钞,你可以隨心所欲地操作字符串。

原生字符串

在 Scala 中聘裁,我們可以直接書(shū)寫(xiě)原生字符串而不用進(jìn)行轉(zhuǎn)義雪营,將字符串內(nèi)容放入一對(duì)三引號(hào)內(nèi)即可:

//包含換行的字符串
val s1= """Welcome here.
   Type "HELP" for help!"""
   
//包含正則表達(dá)式的字符串   
val regex = """\d+"""   

字符串插值

通過(guò) s 表達(dá)式,我們可以很方便地在字符串內(nèi)插值:

val name = "world"
val msg = s"hello, ${name}" // hello, world

集合操作

挑逗指數(shù): 五星

Scala 的集合設(shè)計(jì)是最容易讓人著迷的地方衡便,就像毒品一樣献起,一沾上便讓人深陷其中難以自拔。通過(guò) Scala 提供的集合操作镣陕,我們基本上可以實(shí)現(xiàn) SQL 的全部功能谴餐,這也是為什么 Scala 能夠在大數(shù)據(jù)領(lǐng)域獨(dú)領(lǐng)風(fēng)騷的重要原因之一。

簡(jiǎn)潔的初始化方式

在 Scala 中呆抑,我們可以這樣初始化一個(gè)列表:

val list1 = List(1, 2, 3)

可以這樣初始化一個(gè) Map:

val map = Map("a" -> 1, "b" -> 2)

所有的集合類(lèi)型均可以用類(lèi)似的方式完成初始化岂嗓,簡(jiǎn)潔而富有表達(dá)力。

便捷的 Tuple 類(lèi)型

有時(shí)方法的返回值可能不止一個(gè)鹊碍,Scala 提供了 Tuple (元組)類(lèi)型用于臨時(shí)存放多個(gè)不同類(lèi)型的值厌殉,同時(shí)能夠保證類(lèi)型安全性。千萬(wàn)不要認(rèn)為使用 Java 的 Array 類(lèi)型也可以同樣實(shí)現(xiàn) Tuple 類(lèi)型的功能侈咕,它們之間有著本質(zhì)的區(qū)別公罕。Tuple 會(huì)顯式聲明所有元素的各自類(lèi)型,而不是像 Java Array 那樣耀销,元素類(lèi)型會(huì)被向上轉(zhuǎn)型為所有元素的父類(lèi)型楼眷。
我們可以這樣初始化一個(gè) Tuple:

val t = ("abc", 123, true)
val s: String  = t._1 // 取第1個(gè)元素
val i: Int     = t._2 // 取第2個(gè)元素
val b: Boolean = t._3 // 取第3個(gè)元素

需要注意的是 Tuple 的元素索引從1開(kāi)始。

下面的示例代碼是在一個(gè)長(zhǎng)整型列表中尋找最大值熊尉,并返回這個(gè)最大值以及它所在的位置:

def max(list: List[Long]): (Long, Int) = list.zipWithIndex.sorted.reverse.head

我們通過(guò) zipWithIndex 方法獲取每個(gè)元素的索引號(hào)罐柳,從而將 List[Long] 轉(zhuǎn)換成了 List[(Long, Int)],然后對(duì)其依次進(jìn)行排序狰住、倒序和取首元素硝清,最終返回最大值及其所在位置。

鏈?zhǔn)秸{(diào)用

通過(guò)鏈?zhǔn)秸{(diào)用转晰,我們可以將關(guān)注點(diǎn)放在數(shù)據(jù)的處理和轉(zhuǎn)換上芦拿,而無(wú)需考慮如何存儲(chǔ)和傳遞數(shù)據(jù)士飒,同時(shí)也避免了創(chuàng)建大量無(wú)意義的中間變量,大大增強(qiáng)程序的可讀性蔗崎。其實(shí)上面的 max 函數(shù)已經(jīng)演示了鏈?zhǔn)秸{(diào)用酵幕。下面這段代碼演示了如果在一個(gè)整型列表中尋找大于3的最小奇數(shù):

val list = List(3, 6, 4, 1, 7, 8)
list.filter(i => i % 2 == 1).filter(i => i > 3).sorted.head

非典型集合操作

Scala 的集合操作非常豐富,如果要詳細(xì)說(shuō)明足夠?qū)懸槐緯?shū)了缓苛。這里僅列出一些不那么常用但卻非常好用的操作芳撒。

去重:

List(1, 2, 2, 3).distinct // List(1, 2, 3)

交集:

Set(1, 2) & Set(2, 3)   // Set(2)

并集:

Set(1, 2) | Set(2, 3) // Set(1, 2, 3)

差集:

Set(1, 2) &~ Set(2, 3) // Set(1)

排列:

List(1, 2, 3).permutations.toList
//List(List(1, 2, 3), List(1, 3, 2), List(2, 1, 3), List(2, 3, 1), List(3, 1, 2), List(3, 2, 1))

組合:

List(1, 2, 3).combinations(2).toList 
// List(List(1, 2), List(1, 3), List(2, 3))

并行集合

Scala 的并行集合可以利用多核優(yōu)勢(shì)加速計(jì)算過(guò)程,通過(guò)集合上的 par 方法未桥,我們可以將原集合轉(zhuǎn)換成并行集合笔刹。并行集合利用分治算法將計(jì)算任務(wù)分解成很多子任務(wù),然后交給不同的線程執(zhí)行冬耿,最后將計(jì)算結(jié)果進(jìn)行匯總舌菜。下面是一個(gè)簡(jiǎn)單的示例:

(1 to 10000).par.filter(i => i % 2 == 1).sum

優(yōu)雅的值對(duì)象

挑逗指數(shù): 五星

Case Class

Scala 標(biāo)準(zhǔn)庫(kù)包含了一個(gè)特殊的 Class 叫做 Case Class,專(zhuān)門(mén)用于領(lǐng)域?qū)又祵?duì)象的建模亦镶。它的好處是所有的默認(rèn)行為都經(jīng)過(guò)了合理的設(shè)計(jì)日月,開(kāi)箱即用。下面我們使用 Case Class 定義了一個(gè) User 值對(duì)象:

case class User(name: String, role: String = "user", addTime: Instant = Instant.now())

僅僅一行代碼便完成了 User 類(lèi)的定義缤骨,請(qǐng)腦補(bǔ)一下 Java 的實(shí)現(xiàn)爱咬。

簡(jiǎn)潔的實(shí)例化方式

我們?yōu)?role 和 addTime 兩個(gè)屬性定義了默認(rèn)值,所以我們可以只使用 name 創(chuàng)建一個(gè) User 實(shí)例:

val u = User("jack")

在創(chuàng)建實(shí)例時(shí)绊起,我們也可以命名參數(shù)(named parameter)語(yǔ)法改變默認(rèn)值:

val u = User("jack", role = "admin")

在實(shí)際開(kāi)發(fā)中精拟,一個(gè)模型類(lèi)或值對(duì)象可能擁有很多屬性,其實(shí)很多屬性都可以設(shè)置一個(gè)合理的默認(rèn)值虱歪。利用默認(rèn)值和命名參數(shù)蜂绎,我們可以非常方便地創(chuàng)建模型類(lèi)和值對(duì)象的實(shí)例。
所以在 Scala 中基本上不需要使用工廠模式或構(gòu)造器模式創(chuàng)建對(duì)象实蔽,如果對(duì)象的創(chuàng)建過(guò)程確實(shí)非常復(fù)雜,則可以放在伴生對(duì)象中創(chuàng)建谨读,例如:

object User {
  def apply(name: String): User = User(name, "user", Instant.now())
}

在使用伴生對(duì)象方法創(chuàng)建實(shí)例時(shí)可以省略方法名 apply局装,例如:

User("jack") // 等價(jià)于 User.apply("jack")

在這個(gè)例子里,使用伴生對(duì)象方法實(shí)例化對(duì)象的代碼劳殖,與上面使用類(lèi)構(gòu)造器的代碼完全一樣铐尚,編譯器會(huì)優(yōu)先選擇伴生對(duì)象的 apply 方法。

不可變性

Case Class 的實(shí)例是不可變的哆姻,意味著它可以被任意共享宣增,并發(fā)訪問(wèn)時(shí)也無(wú)需同步,大大地節(jié)省了寶貴的內(nèi)存空間矛缨。而在 Java 中爹脾,對(duì)象被共享時(shí)需要進(jìn)行深拷貝帖旨,否則一個(gè)地方的修改會(huì)影響到其它地方。例如在 Java 中定義了一個(gè) Role 對(duì)象:

public class Role {
    public String id = "";
    public String name = "user";
    
    public Role(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

如果在兩個(gè) User 之間共享 Role 實(shí)例就會(huì)出現(xiàn)問(wèn)題灵妨,就像下面這樣:

u1.role = new Role("user", "user");
u2.role = u1.role;

當(dāng)我們修改 u1.role 時(shí)解阅,u2 就會(huì)受到影響,Java 的解決方式是要么基于 u1.role 深度克隆一個(gè)新對(duì)象出來(lái)泌霍,要么新創(chuàng)建一個(gè) Role 對(duì)象賦值給 u2货抄。

對(duì)象拷貝

在 Scala 中,既然 Case Class 是不可變的朱转,那么如果想改變它的值該怎么辦呢蟹地?其實(shí)很簡(jiǎn)單,利用命名參數(shù)可以很容易拷貝一個(gè)新的不可變對(duì)象出來(lái):

val u1 = User("jack")
val u2 = u1.copy(name = "role", role = "admin")

清晰的調(diào)試信息

我們不需要編寫(xiě)額外的代碼便可以得到清晰的調(diào)試信息藤为,例如:

val users = List(User("jack"), User("rose"))
println(users)

輸出內(nèi)容如下:

List(User(jack,user,2018-10-20T13:03:16.170Z), User(rose,user,2018-10-20T13:03:16.170Z))

默認(rèn)使用值比較相等性

在 Scala 中怪与,默認(rèn)采用值比較而非引用比較,使用起來(lái)更加符合直覺(jué):

User("jack") == User("jack") // true

上面的值比較是開(kāi)箱即用的凉蜂,無(wú)需重寫(xiě) hashCode 和 equals 方法琼梆。

模式匹配

挑逗指數(shù): 五星

更強(qiáng)的可讀性

當(dāng)你的代碼中存在多個(gè) if 分支并且 if 之間還會(huì)有嵌套,那么代碼的可讀性將會(huì)大大降低窿吩。而在 Scala 中使用模式匹配可以很容易地解決這個(gè)問(wèn)題茎杂,下面的代碼演示貨幣類(lèi)型的匹配:

sealed trait Currency
case class Dollar(value: Double) extends Currency
case class Euro(value: Double) extends Currency
val Currency = ...
currency match {
    case Dollar(v) => "$" + v
    case Euro(v) => "€" + v
    case _ => "unknown"
}

我們也可以進(jìn)行一些復(fù)雜的匹配,并且在匹配時(shí)可以增加 if 判斷:

use match {
    case User("jack", _, _) => ...
    case User(_, _, addTime) if addTime.isAfter(time) => ...
    case _ => ...
}

變量賦值

利用模式匹配纫雁,我們可以快速提取特定部分的值并完成變量定義煌往。
我們可以將 Tuple 中的值直接賦值給變量:

val tuple = ("jack", "user", Instant.now())
val (name, role, addTime) = tuple
// 變量 name, role, addTime 在當(dāng)前作用域內(nèi)可以直接使用

對(duì)于 Case Class 也是一樣:

val User(name, role, addTime) = User("jack")
// 變量 name, role, addTime 在當(dāng)前作用域內(nèi)可以直接使用

并發(fā)編程

挑逗指數(shù): 五星

在 Scala 中,我們?cè)诰帉?xiě)并發(fā)代碼時(shí)只需要關(guān)心業(yè)務(wù)邏輯即可轧邪,而并不需要關(guān)注底層的線程池如何分配刽脖。Future 用于啟動(dòng)一個(gè)異步任務(wù)并且保存執(zhí)行結(jié)果,每個(gè) Future 都在獨(dú)立的線程中運(yùn)行忌愚。我們可以用 for 表達(dá)式收集多個(gè) Future 的執(zhí)行結(jié)果曲管,從而避免回調(diào)地獄:

val f1 = Future{ 1 + 2 }
val f2 = Future{ 3 + 4 }
for {
    v1 <- f1
    v2 <- f2
}{
    println(v1 + v2) // 10
}

使用 Future 開(kāi)發(fā)爬蟲(chóng)程序?qū)?huì)讓你事半功倍,假如你想同時(shí)抓取 100 個(gè)頁(yè)面數(shù)據(jù)硕糊,一行代碼就可以了:

Future.sequence(urls.map(url => http.get(url))).forEach{ contents => ...}

Future.sequence 方法用于收集所有 Future 的執(zhí)行結(jié)果院水,通過(guò) forEach 方法我們可以取出收集結(jié)果并進(jìn)行后續(xù)處理。

當(dāng)我們要實(shí)現(xiàn)完全異步的請(qǐng)求限流時(shí)简十,就需要精細(xì)地控制每個(gè) Future 的執(zhí)行時(shí)機(jī)檬某。也就是說(shuō)我們需要一個(gè)控制Future的開(kāi)關(guān),沒(méi)錯(cuò)螟蝙,這個(gè)開(kāi)關(guān)就是Promise恢恼。每個(gè)Promise實(shí)例都會(huì)有一個(gè)唯一的Future與之相關(guān)聯(lián):

val p = Promise[Int]()
val f = p.future
for (v <- f) { println(v) } // 3秒后才會(huì)執(zhí)行打印操作

//3秒鐘之后返回3
Thread.sleep(3000)
p.success(3)

跨線程錯(cuò)誤處理

Java 通過(guò)異常機(jī)制處理錯(cuò)誤,但是問(wèn)題在于 Java 代碼只能捕獲當(dāng)前線程的異常胰默,而無(wú)法跨線程捕獲異常场斑。而在 Scala 中漓踢,我們可以通過(guò) Future 捕獲任意線程中發(fā)生的異常。
異步任務(wù)可能成功也可能失敗和簸,所以我們需要一種既可以表示成功彭雾,也可以表示失敗的數(shù)據(jù)類(lèi)型,在 Scala 中它就是 Try[T]锁保。Try[T] 有兩個(gè)子類(lèi)型薯酝,Success[T]表示成功,F(xiàn)ailure[T]表示失敗爽柒。就像量子物理學(xué)中薛定諤的貓吴菠,在異步任務(wù)執(zhí)行之前,你根本無(wú)法預(yù)知返回的結(jié)果是 Success[T] 還是 Failure[T]浩村,只有當(dāng)異步任務(wù)完成執(zhí)行以后結(jié)果才能確定下來(lái)做葵。

val f = Future{ /*異步任務(wù)*/ } 

// 當(dāng)異步任務(wù)執(zhí)行完成時(shí)
f.value.get match {
  case Success(v) => // 處理成功情況
  case Failure(t) => // 處理失敗情況
}

我們也可以讓一個(gè) Future 從錯(cuò)誤中恢復(fù):

val f = Future{ /*異步任務(wù)*/ }
for{
  result <- f.recover{ case t => /*處理錯(cuò)誤*/ }
} yield {
  // 處理結(jié)果
}

聲明式編程

挑逗指數(shù): 四星

Scala 鼓勵(lì)聲明式編程,采用聲明式編寫(xiě)的代碼可讀性更強(qiáng)心墅。與傳統(tǒng)的過(guò)程式編程相比酿矢,聲明式編程更關(guān)注我想做什么而不是怎么去做。例如我們經(jīng)常要實(shí)現(xiàn)分頁(yè)操作怎燥,每頁(yè)返回 10 條數(shù)據(jù):

val allUsers = List(User("jack"), User("rose"))
val pageList = 
  allUsers
    .sortBy(u => (u.role, u.name, u.addTime)) // 依次按 role, name, addTime 進(jìn)行排序
    .drop(page * 10) // 跳過(guò)之前頁(yè)數(shù)據(jù)
    .take(10) // 取當(dāng)前頁(yè)數(shù)據(jù)瘫筐,如不足10個(gè)則全部返回

你只需要告訴 Scala 要做什么,比如說(shuō)先按 role 排序铐姚,如果 role 相同則按 name 排序策肝,如果 role 和 name 都相同,再按 addTime 排序隐绵。底層具體的排序?qū)崿F(xiàn)已經(jīng)封裝好了之众,開(kāi)發(fā)者無(wú)需實(shí)現(xiàn)。

面向表達(dá)式編程

挑逗指數(shù): 四星

在 Scala 中依许,一切都是表達(dá)式棺禾,包括 if, for, while 等常見(jiàn)的控制結(jié)構(gòu)均是表達(dá)式。表達(dá)式和語(yǔ)句的不同之處在于每個(gè)表達(dá)式都有明確的返回值峭跳。

val i = if(true){ 1 } else { 0 } // i = 1
val list1 = List(1, 2, 3)
val list2 = for(i <- list1) yield { i + 1 }

不同的表達(dá)式可以組合在一起形成一個(gè)更大的表達(dá)式膘婶,再結(jié)合上模式匹配將會(huì)發(fā)揮巨大的威力。下面我們以一個(gè)計(jì)算加法的解釋器來(lái)做說(shuō)明坦康。

一個(gè)整數(shù)加法解釋器

我們首先定義基本的表達(dá)式類(lèi)型:

abstract class Expr
case class Number(num: Int) extends Expr
case class PlusExpr(left: Expr, right: Expr) extends Expr

上面定義了兩個(gè)表達(dá)式類(lèi)型竣付,Number 表示一個(gè)整數(shù)表達(dá)式诡延, PlusExpr 表示一個(gè)加法表達(dá)式滞欠。
下面我們基于模式匹配實(shí)現(xiàn)表達(dá)式的求值運(yùn)算:

def evalExpr(expr: Expr): Int = {
  expr match {
    case Number(n) => n
    case PlusExpr(left, right) => evalExpr(left) + evalExpr(right)
  }
}

我們來(lái)嘗試針對(duì)一個(gè)較大的表達(dá)式進(jìn)行求值:

evalExpr(PlusExpr(PlusExpr(Number(1), Number(2)), PlusExpr(Number(3), Number(4)))) // 10

隱式參數(shù)和隱式轉(zhuǎn)換

挑逗指數(shù): 五星

隱式參數(shù)

如果每當(dāng)要執(zhí)行異步任務(wù)時(shí),都需要顯式傳入線程池參數(shù)肆良,你會(huì)不會(huì)覺(jué)得很煩筛璧?Scala 通過(guò)隱式參數(shù)為你解除這個(gè)煩惱逸绎。例如 Future 在創(chuàng)建異步任務(wù)時(shí)就聲明了一個(gè) ExecutionContext 類(lèi)型的隱式參數(shù),編譯器會(huì)自動(dòng)在當(dāng)前作用域內(nèi)尋找合適的 ExecutionContext夭谤,如果找不到則會(huì)報(bào)編譯錯(cuò)誤:

implicit val ec: ExecutionContext = ???
val f = Future { /*異步任務(wù)*/ }

當(dāng)然我們也可以顯式傳遞 ExecutionContext 參數(shù)棺牧,明確指定使用的線程池:

implicit val ec: ExecutionContext = ???
val f = Future { /*異步任務(wù)*/ }(ec)

隱式轉(zhuǎn)換

隱式轉(zhuǎn)換相比較于隱式參數(shù),使用起來(lái)更來(lái)靈活朗儒。如果 Scala 在編譯時(shí)發(fā)現(xiàn)了錯(cuò)誤颊乘,在報(bào)錯(cuò)之前,會(huì)先對(duì)錯(cuò)誤代碼應(yīng)用隱式轉(zhuǎn)換規(guī)則醉锄,如果在應(yīng)用規(guī)則之后可以使得其通過(guò)編譯乏悄,則表示成功地完成了一次隱式轉(zhuǎn)換。

在不同的庫(kù)間實(shí)現(xiàn)無(wú)縫對(duì)接

當(dāng)傳入的參數(shù)類(lèi)型和目標(biāo)類(lèi)型不匹配時(shí)恳不,編譯器會(huì)嘗試隱式轉(zhuǎn)換檩小。利用這個(gè)功能,我們將已有的數(shù)據(jù)類(lèi)型無(wú)縫對(duì)接到三方庫(kù)上烟勋。例如我們想在 Scala 項(xiàng)目中使用 MongoDB 的官方 Java 驅(qū)動(dòng)執(zhí)行數(shù)據(jù)庫(kù)查詢(xún)操作,但是查詢(xún)接口接受的參數(shù)類(lèi)型是 BsonDocument,由于使用 BsonDocument 構(gòu)建查詢(xún)比較笨拙奥秆,我們希望能夠使用 Scala 的 JSON 庫(kù)構(gòu)建一個(gè)查詢(xún)對(duì)象谈竿,然后直接傳遞給官方驅(qū)動(dòng)的查詢(xún)接口,而無(wú)需改變官方驅(qū)動(dòng)的任何代碼鸵荠,利用隱式轉(zhuǎn)換可以非常輕松地實(shí)現(xiàn)這個(gè)功能:

implicit def toBson(json: JsObject): BsonDocument =  ...

val json: JsObject = Json.obj("_id" -> "0")
jCollection.find(json) // 編譯器會(huì)自動(dòng)調(diào)用 toBson(json)

利用隱式轉(zhuǎn)換冕茅,我們可以在不改動(dòng)三方庫(kù)代碼的情況下,將我們的數(shù)據(jù)類(lèi)型與其進(jìn)行無(wú)縫對(duì)接蛹找。例如我們通過(guò)實(shí)現(xiàn)一個(gè)隱式轉(zhuǎn)換姨伤,將 Scala 的 JsObject 類(lèi)型無(wú)縫地對(duì)接到了 MongoDB 的官方 Java 驅(qū)動(dòng)的查詢(xún)接口中,看起就像是 MongoDB 官方驅(qū)動(dòng)真的提供了這個(gè)接口一樣庸疾。

同時(shí)我們也可以將來(lái)自三方庫(kù)的數(shù)據(jù)類(lèi)型無(wú)縫集成到現(xiàn)有的接口中乍楚,也只需要實(shí)現(xiàn)一個(gè)隱式轉(zhuǎn)換方法即可。

擴(kuò)展已有類(lèi)的功能

例如我們定義了一個(gè)美元貨幣類(lèi)型 Dollar:

class Dollar(value: Double) {
  def + (that: Dollar): Dollar = ...
  def + (that: Int): Dollar = ...
}

于是我們可以執(zhí)行如下操作:

val halfDollar = new Dollar(0.5)
halfDollar + halfDollar // 1 dollar
halfDollar + 0.5 // 1 dollar

但是我們卻無(wú)法執(zhí)行像 0.5 + halfDollar 這樣的運(yùn)算届慈,因?yàn)樵?Double 類(lèi)型上無(wú)法找到一個(gè)合適的 + 方法徒溪。

在 Scala 中,為了實(shí)現(xiàn)上面的運(yùn)算金顿,我們只需要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的隱式轉(zhuǎn)換就可以了:

implicit def doubleToDollar(d: Double) = new Dollar(d)

0.5 + halfDollar // 等價(jià)于 doubleToDollar(0.5) + halfDollar

更好的運(yùn)行時(shí)性能

在日常開(kāi)發(fā)中臊泌,我們通常需要將值對(duì)象轉(zhuǎn)換成 Json 格式以方便數(shù)據(jù)傳輸。Java 的通常做法是使用反射揍拆,但是我們知道使用反射是要付出代價(jià)的渠概,要承受運(yùn)行時(shí)的性能開(kāi)銷(xiāo)。而 Scala 則可以在編譯時(shí)為值對(duì)象生成隱式的 Json 編解碼器,這些編解碼器只不過(guò)是普通的函數(shù)調(diào)用而已播揪,不涉及任何反射操作贮喧,在很大程度上提升了系統(tǒng)的運(yùn)行時(shí)性能。

小結(jié)

如果你堅(jiān)持讀到了這里猪狈,我會(huì)覺(jué)得非常欣慰箱沦,很大可能上 Scala 的某些特性已經(jīng)吸引了你。但是 Scala 的魅力遠(yuǎn)不止如此雇庙,以上列舉的僅僅是一些最容易抓住你眼球的一些特性谓形。如果你愿意推開(kāi) Scala 這扇大門(mén),你將會(huì)看到一個(gè)完全不一樣的編程世界疆前。本文歡迎轉(zhuǎn)載套耕,請(qǐng)注明作者沐風(fēng)(joymufeng)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末峡继,一起剝皮案震驚了整個(gè)濱河市冯袍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碾牌,老刑警劉巖康愤,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異舶吗,居然都是意外死亡征冷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)誓琼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)检激,“玉大人,你說(shuō)我怎么就攤上這事腹侣∈迨眨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵傲隶,是天一觀的道長(zhǎng)饺律。 經(jīng)常有香客問(wèn)我,道長(zhǎng)跺株,這世上最難降的妖魔是什么复濒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮乒省,結(jié)果婚禮上巧颈,老公的妹妹穿的比我還像新娘。我一直安慰自己袖扛,他們只是感情好砸泛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般晾嘶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上娶吞,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天垒迂,我揣著相機(jī)與錄音,去河邊找鬼妒蛇。 笑死机断,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绣夺。 我是一名探鬼主播吏奸,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼陶耍!你這毒婦竟也來(lái)了奋蔚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤烈钞,失蹤者是張志新(化名)和其女友劉穎泊碑,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體毯欣,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馒过,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酗钞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腹忽。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖砚作,靈堂內(nèi)的尸體忽然破棺而出窘奏,到底是詐尸還是另有隱情,我是刑警寧澤葫录,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布蔼夜,位于F島的核電站,受9級(jí)特大地震影響压昼,放射性物質(zhì)發(fā)生泄漏求冷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一窍霞、第九天 我趴在偏房一處隱蔽的房頂上張望匠题。 院中可真熱鬧,春花似錦但金、人聲如沸韭山。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钱磅。三九已至梦裂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盖淡,已是汗流浹背年柠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留褪迟,地道東北人冗恨。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像味赃,于是被迫代替她去往敵國(guó)和親掀抹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 五個(gè)人里心俗,最笨的非邱瑩瑩莫屬傲武。 楊紫演的邱瑩瑩,眼神里分明是滴溜溜的機(jī)靈勁城榛,為了表現(xiàn)出邱瑩瑩的魯莽沖動(dòng)谱轨,她故意將動(dòng)...
    玄玄被用了閱讀 828評(píng)論 0 2
  • 實(shí)例5:IP地址歸屬地的自動(dòng)查詢(xún) IP138網(wǎng)址可以提交后查詢(xún)IP地址,那么在程序中怎么查詢(xún)IP地址呢吠谢? IP地址...
    饕餮思文閱讀 238評(píng)論 0 1
  • 夢(mèng)想已制定土童,但久不檢查達(dá)成情況,容易忘記工坊,產(chǎn)生思想上忽視献汗。GTD4.0學(xué)習(xí)團(tuán)隊(duì)要求每日自檢反思,我想也是為了防止這...
    rong13900閱讀 203評(píng)論 0 0
  • 01 我玩微信的時(shí)候,看到小學(xué)同學(xué)文文在朋友圈里發(fā)的一條狀態(tài)昭齐,說(shuō)要大家給她介紹工作尿招。我不敢點(diǎn)贊,不敢評(píng)論阱驾,默默地路...
    邵悅婷閱讀 755評(píng)論 1 3