自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開(kāi)發(fā) 后端 新聞
有個(gè)問(wèn)題一直困擾著 Scala 社區(qū),為什么一些 Java 開(kāi)發(fā)者將 Scala 捧到了天上,認(rèn)為它是來(lái)自上帝之吻的完美語(yǔ)言;而另外一些 Java 開(kāi)發(fā)者卻對(duì)它望而卻步,認(rèn)為它過(guò)于復(fù)雜而難以理解。

有個(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)。

[[247336]]

類(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)型信息,例如:

  1. List numbers = new ArrayList<>(); 

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

  1. var numbers = new ArrayList<>(); 

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

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

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

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

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

  1. public boolean userExistsIn(Set<Long> userIds) { 
  2.     String userId = getCurrentUserId(); 
  3.     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ò)編譯器法眼的:

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

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

字符串增強(qiáng)

  • 挑逗指數(shù): 四星

常用操作

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

  1. //字符串去重 
  2. "aabbcc".distinct // "abc" 
  3.  
  4. //取前n個(gè)字符,如果n大于字符串長(zhǎng)度返回原字符串 
  5. "abcd".take(10) // "abcd" 
  6.  
  7. //字符串排序 
  8. "bcad".sorted // "abcd" 
  9.  
  10. //過(guò)濾特定字符 
  11. "bcad".filter(_ != 'a') // "bcd" 
  12.  
  13. //類(lèi)型轉(zhuǎn)換 
  14. "true".toBoolean 
  15. "123".toInt 
  16. "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)即可:

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

字符串插值

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

  1. val name = "world"  
  2. 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è)列表:

  1. val list1 = List(1, 2, 3) 

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

  1. 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:

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

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

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

  1. 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ù):

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

非典型集合操作

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

去重:

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

交集:

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

并集:

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

差集:

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

排列:

  1. List(1, 2, 3).permutations.toList 
  2. //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)) 

組合:

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

并行集合

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

  1. (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ì)象:

  1. 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í)例:

  1. val u = User("jack"

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

  1. 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 中基本上不需要使用工廠(chǎng)模式或構(gòu)造器模式創(chuàng)建對(duì)象,如果對(duì)象的創(chuàng)建過(guò)程確實(shí)非常復(fù)雜,則可以放在伴生對(duì)象中創(chuàng)建,例如:

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

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

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

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

不可變性

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

  1. public class Role { 
  2.     public String id = ""
  3.     public String name = "user"
  4.      
  5.     public Role(String id, String name) { 
  6.         this.id = id; 
  7.         this.name = name
  8.     } 

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

  1. u1.role = new Role("user""user"); 
  2. 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):

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

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

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

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

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

  1. 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é):

  1. 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)型的匹配:

  1. sealed trait Currency 
  2. case class Dollar(value: Double) extends Currency 
  3. case class Euro(value: Double) extends Currency 
  4. val Currency = ... 
  5. currency match { 
  6.     case Dollar(v) => "$" + v 
  7.     case Euro(v) => "€" + v 
  8.     case _ => "unknown" 

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

  1. use match { 
  2.     case User("jack", _, _) => ... 
  3.     case User(_, _, addTime) if addTime.isAfter(time) => ... 
  4.     case _ => ... 

變量賦值

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

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

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

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

并發(fā)編程

  • 挑逗指數(shù): 五星

在 Scala 中,我們?cè)诰帉?xiě)并發(fā)代碼時(shí)只需要關(guān)心業(yè)務(wù)邏輯即可,而不需要關(guān)注任務(wù)如何執(zhí)行。我們可以通過(guò)顯式或隱式方式傳入一個(gè)線(xiàn)程池,具體的執(zhí)行過(guò)程由線(xiàn)程池完成。Future 用于啟動(dòng)一個(gè)異步任務(wù)并且保存執(zhí)行結(jié)果,我們可以用 for 表達(dá)式收集多個(gè) Future 的執(zhí)行結(jié)果,從而避免回調(diào)地獄:

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

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

  1. 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):

  1. val p = Promise[Int]() 
  2. val f = p.future 
  3. for (v <- f) { println(v) } // 3秒后才會(huì)執(zhí)行打印操作 
  4.  
  5. //3秒鐘之后返回3 
  6. Thread.sleep(3000) 
  7. p.success(3) 

跨線(xiàn)程錯(cuò)誤處理

Java 通過(guò)異常機(jī)制處理錯(cuò)誤,但是問(wèn)題在于 Java 代碼只能捕獲當(dāng)前線(xiàn)程的異常,而無(wú)法跨線(xiàn)程捕獲異常。而在 Scala 中,我們可以通過(guò) Future 捕獲任意線(xiàn)程中發(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)。

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

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

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

聲明式編程

  • 挑逗指數(shù): 四星

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

  1. val allUsers = List(User("jack"), User("rose")) 
  2. val pageList =  
  3.   allUsers 
  4.     .sortBy(u => (u.role, u.name, u.addTime)) // 依次按 role, name, addTime 進(jìn)行排序 
  5.     .drop(page * 10) // 跳過(guò)之前頁(yè)數(shù)據(jù) 
  6.     .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á)式都有明確的返回值。

  1. val i = if(true){ 1 } else { 0 } // i = 1 
  2. val list1 = List(1, 2, 3) 
  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)型:

  1. abstract class Expr 
  2. case class Number(num: Int) extends Expr 
  3. 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)算:

  1. def evalExpr(expr: Expr): Int = { 
  2.   expr match { 
  3.     case Number(n) => n 
  4.     case PlusExpr(leftright) => evalExpr(left) + evalExpr(right
  5.   } 

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

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

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

  • 挑逗指數(shù): 五星

隱式參數(shù)

如果每當(dāng)要執(zhí)行異步任務(wù)時(shí),都需要顯式傳入線(xiàn)程池參數(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ò)誤:

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

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

  1. implicit val ec: ExecutionContext = ??? 
  2. 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è)功能:

  1. implicit def toBson(json: JsObject): BsonDocument =  ... 
  2.  
  3. val json: JsObject = Json.obj("_id" -> "0"
  4. 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:

  1. class Dollar(value: Double) {  
  2. def + (that: Dollar): Dollar = ...  
  3. def + (that: Int): Dollar = ...  

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

  1. val halfDollar = new Dollar(0.5)  
  2. halfDollar + halfDollar // 1 dollar  
  3. 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)換就可以了:

  1. implicit def doubleToDollar(d: Double) = new Dollar(d)  
  2. 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è)完全不一樣的編程世界。

責(zé)任編輯:未麗燕 來(lái)源: 開(kāi)源社區(qū)
相關(guān)推薦

2013-04-15 10:55:09

程序員

2013-05-24 09:14:39

國(guó)企程序員程序員

2012-11-09 13:44:48

ScalaJVMJava

2015-08-19 09:10:37

程序員面試

2018-02-06 08:36:02

簡(jiǎn)歷程序員面試

2009-05-26 09:00:59

ScalaJava面向?qū)ο?/a>

2022-04-18 11:05:36

開(kāi)源github代碼庫(kù)

2016-12-19 17:35:58

程序員特質(zhì)

2020-02-28 15:49:26

2021-08-28 23:26:14

程序員編碼電腦

2013-08-20 09:33:59

程序員

2014-07-29 10:30:16

JavaJava程序員

2019-07-03 14:47:43

程序員祼辭就業(yè)

2015-04-09 13:52:47

程序員在家辦公

2020-03-05 11:02:38

程序員編程書(shū)籍

2011-03-22 10:49:53

2012-11-08 09:49:30

C++Java程序員

2011-06-01 09:49:55

程序員IT

2015-11-06 10:22:56

程序員笑話(huà)

2012-11-02 13:47:31

Java程序員編程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)