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

從Java走進(jìn)Scala:Scala控制結(jié)構(gòu)內(nèi)部揭密

開(kāi)發(fā) 后端
Scala 是專為 Java平臺(tái)編寫的,因此其語(yǔ)法設(shè)計(jì)會(huì)使 Java 代碼編碼人員感覺(jué)很輕松。同時(shí),Scala 為 JVM 提供了函數(shù)語(yǔ)言的固有的強(qiáng)大功能,并以這些函數(shù)設(shè)計(jì)概念為出發(fā)點(diǎn)。在本文中,Ted Neward 將介紹兩種語(yǔ)言之間的細(xì)微差異,從一些控制結(jié)構(gòu)(比如 if、while 和 for)開(kāi)始介紹。正如您將要學(xué)習(xí)到的那樣,Scala 為這些結(jié)構(gòu)提供了一些在其 Java 等效物中無(wú)法獲得的功能和復(fù)雜性。

迄今為止,在此系列中,我們已經(jīng)討論了 Scala 對(duì)生態(tài)環(huán)境的保真度,展示了 Scala 如何將眾多的 Java 核心對(duì)象功能合并在一起。如果 Scala 只是編寫對(duì)象的另一種方式,那么它不會(huì)有任何引人注意的地方,或者說(shuō)不再那么功能強(qiáng)大。Scala 的函數(shù)概念和對(duì)象概念的合并,以及它對(duì)編程人員效率的重視,這些使得學(xué)習(xí) Scala 語(yǔ)言比 Java-cum-Scala 編程人員所想象的體驗(yàn)更加復(fù)雜、更加微妙。

例如,對(duì)控制結(jié)構(gòu)(比如 if、while 和 for)使用 Scala 的方法。盡管這些控制結(jié)構(gòu)看起來(lái)類似一些老的、還比較不錯(cuò)的 Java 結(jié)構(gòu),但實(shí)際上 Scala 為它們?cè)黾恿艘恍┩耆煌奶匦?。本月的文章是關(guān)于使用 Scala 控制結(jié)構(gòu)時(shí)能夠期望獲得哪些東西的入門級(jí)讀物,而不是在制造許多錯(cuò)誤(并編寫一堆錯(cuò)誤代碼)之后,讓您冒著遭受挫折的風(fēng)險(xiǎn)去尋找差異。

修訂后的 Person.scala

在 本系列的上一篇文章 中,可以了解到 Scala 能夠通過(guò)定義一些方法來(lái)定義 POJO,這些方法模仿基于 POJO 的環(huán)境所需的傳統(tǒng) “getter 和 setter”。在這篇文章發(fā)表之后,我收到了 Bill Venners 發(fā)來(lái)的電子郵件,Bill Venners 是即將發(fā)表的正式的 Scala 參考資料使用 Scala 編程(請(qǐng)參閱 參考資料)的合著者之一。Bill 指出了實(shí)現(xiàn)上述操作的一個(gè)更簡(jiǎn)單的方法,即使用 scala.reflect.BeanProperty 標(biāo)注,如下所示:

清單 1. 修改后的 Person.scala 

  1. class Person(fn:String, ln:String, a:Int)  
  2.    {  
  3. @scala.reflect.BeanProperty  
  4. var firstName = fn  
  5.  
  6. @scala.reflect.BeanProperty  
  7. var lastName = ln  
  8.  
  9. @scala.reflect.BeanProperty  
  10. var age = a  
  11.  
  12. override def toString =  
  13.     "[Person firstName:" + firstName + " lastName:" + lastName +  
  14.  " age:" + age + " ]" 
  15.    }  
  16.       

清單 1 中的方法(上一篇文章 中的清單 13 的修訂版)為指定的 var 生成了 get/set 方法對(duì)。惟一的缺陷是這些方法并不實(shí)際存在于 Scala 代碼中,因此其他 Scala 代碼無(wú)法調(diào)用它們。這通常不是什么大問(wèn)題,因?yàn)?Scala 將對(duì)為自己生成的字段使用已生成的方法;如果事先不知道,那么這些對(duì)您而言可能是一個(gè)驚喜。

在查看了清單 1 中的代碼之后,最讓我感到震動(dòng)的是,Scala 并沒(méi)有只演示組合函數(shù)概念和對(duì)象概念的強(qiáng)大威力,它還演示了自 Java ***發(fā)布之后的 30 年里對(duì)象語(yǔ)言帶來(lái)的一些益處。

控制是一種幻想

您將看到的許多奇怪的、不可思議的東西都可以歸功于 Scala 的函數(shù)特性,因此,簡(jiǎn)單介紹一下函數(shù)語(yǔ)言開(kāi)發(fā)和演變的背景可能非常有用。

在函數(shù)語(yǔ)言中,將越來(lái)越高級(jí)的結(jié)構(gòu)直接構(gòu)建到語(yǔ)言中是不常見(jiàn)的。此外,語(yǔ)言是通過(guò)一組核心原語(yǔ)結(jié)構(gòu)定義的。在與將函數(shù)作為對(duì)象傳遞的功能結(jié)合之后,可用來(lái)定義功能的高階函數(shù) 看起來(lái) 像是超出了核心語(yǔ)言的范圍,但實(shí)際上它只是一個(gè)庫(kù)。類似于任何庫(kù),此功能可以替換、擴(kuò)充或擴(kuò)展。

根據(jù)一組核心原語(yǔ)構(gòu)建語(yǔ)言的合成 特性由來(lái)已久,可以追溯到 20 世紀(jì) 60 年代和 70 年代使用 Smalltalk、Lisp 和 Scheme 的時(shí)候。諸如 Lisp 和 Scheme 之類的語(yǔ)言因?yàn)樗鼈冊(cè)诟图?jí)別的抽象上定義更高級(jí)別抽象的能力而受到人們的狂熱追捧。編程人員可以使用高級(jí)抽象,用它們構(gòu)建更高級(jí)的抽象。如今聽(tīng)到討論這個(gè)過(guò)程時(shí),它通常是關(guān)于特定于域的語(yǔ)言(或 DSL)的(請(qǐng)參閱 參考資料)。實(shí)際上,它只是關(guān)于如何在抽象之上構(gòu)建抽象的過(guò)程。

在 Java 語(yǔ)言中,惟一選擇就是利用 API 調(diào)用完成此操作;在 Scala 中,可以通過(guò)擴(kuò)展語(yǔ)言本身實(shí)現(xiàn)它。試圖擴(kuò)展 Java 語(yǔ)言會(huì)帶來(lái)創(chuàng)建極端場(chǎng)景(corner case)的風(fēng)險(xiǎn),這些場(chǎng)景將威脅全局的穩(wěn)定性。而試圖擴(kuò)展 Scala 則只意味著創(chuàng)建一個(gè)新庫(kù)。

#p#

If 結(jié)構(gòu)

我們將從傳統(tǒng)的 if 結(jié)構(gòu)開(kāi)始 —— 當(dāng)然,此結(jié)構(gòu)必須是最容易處理的結(jié)構(gòu)之一,不是嗎?畢竟,從理論上說(shuō),if 只檢查一個(gè)條件。如果條件為真,則執(zhí)行后面跟著的代碼。

但是,這種簡(jiǎn)單性可能帶有欺騙性。傳統(tǒng)上,Java 語(yǔ)言對(duì) if 的 else 子句的使用是隨意的,并且假定如果條件出錯(cuò),可以只跳過(guò)代碼塊。但在函數(shù)語(yǔ)句中,情況不是這樣。為了保持函數(shù)語(yǔ)句的算術(shù)特性,所有一切都必須以表達(dá)式計(jì)算的方式出現(xiàn),包括 if 子句本身(對(duì)于 Java 開(kāi)發(fā)人員,這正是三元操作符 —— ?: 表達(dá)式 —— 的工作方式)。

在 Scala 中,非真代碼塊(代碼塊的 else 部分)必須以與 if 代碼塊中值種類相同的形式呈現(xiàn),并且必須產(chǎn)生同一種類的值。這意味著不論以何種方式執(zhí)行代碼,總會(huì)產(chǎn)生一個(gè)值。例如,請(qǐng)參見(jiàn)以下 Java 代碼:

清單 2. 哪個(gè)配置文件?(Java 版)

  1. // This is Java  
  2. String filename = "default.properties";  
  3. if (options.contains("configFile"))  
  4.   filename = (String)options.get("configFile"); 

因?yàn)?Scala 中的 if 結(jié)構(gòu)自身就是一個(gè)表達(dá)式,所以重寫上述代碼會(huì)使它們成為清單 3 中所示的更正確的代碼片段:

清單 3. 哪個(gè)配置文件?(Scala 版)

  1. // This is Scala  
  2. val filename =  
  3.   if (options.contains("configFile"))  
  4.     options.get("configFile")  
  5.   else 
  6.     "default.properties" 

也就是說(shuō),Scala 編程人員通常應(yīng)該*** val 結(jié)構(gòu),并在明確需要可變性的時(shí)候選擇 var。原因很簡(jiǎn)單:除了使編程更容易之外,val 還能確保程序的線程安全性,Scala 中的一個(gè)內(nèi)在主題是:幾乎每次認(rèn)為需要可變狀態(tài)時(shí),其實(shí)都不需要可變狀態(tài)。讓我們從不可變字段和本地變量(val)開(kāi)始,這是展示上述情況的一種方法,甚至對(duì)最堅(jiān)定的 Java 懷疑論者也是如此。從 Java 中的 final 開(kāi)始介紹可能不是很合理,或許是因?yàn)?Java 的非函數(shù)特性,盡管此原因不可取。一些好奇的 Java 開(kāi)發(fā)人員可能想嘗試一下。
 
盡管真正的贏家是 Scala,但可以通過(guò)編寫代碼將結(jié)果分配給 val,而不是 var。在設(shè)置之后,就無(wú)法對(duì) val 進(jìn)行更改,這與 Java 語(yǔ)言中 final 變量的操作方式是相同的。不可變本地變量最顯著的副作用是很容易實(shí)現(xiàn)并發(fā)性。試圖用 Java 代碼實(shí)現(xiàn)同樣的操作時(shí),會(huì)帶來(lái)許多不錯(cuò)的、易讀的好代碼,如清單 4 中所示:

清單 4. 哪個(gè)配置文件?(Java 版,三元式)

  1. //This is Java  
  2. final String filename =  
  3.   options.contains("configFile") ?  
  4.     options.get("configFile") : "default.properties"

用代碼評(píng)審解釋這一點(diǎn)可能需要點(diǎn)技巧。也許這樣做是正確的,但許多 Java 編程人員會(huì)不以為然并且詢問(wèn) “您做那個(gè)干什么”?

val 與 var

您可能想更多地了解 val 與 var 之間的不同,實(shí)際上,它們的不同之處在于 —— 一個(gè)是只讀的值,另一個(gè)是可變的變量。通常,函數(shù)語(yǔ)言,特別是被認(rèn)為是 “純” 函數(shù)語(yǔ)言(不允許帶有副作用,比如可變狀態(tài))的那些函數(shù)語(yǔ)言,只支持 val 概念;但是,因?yàn)?Scala 要同時(shí)吸引函數(shù)編程人員和命令/對(duì)象編程人員,所以這二種結(jié)構(gòu)它都提供。

已公開(kāi)的 while 結(jié)構(gòu)

接下來(lái),讓我們來(lái)看一下 while 及其同胞 do-while。它們做的基本上是同一件事:測(cè)試一個(gè)條件,如果該條件為真,則繼續(xù)執(zhí)行提供的代碼塊。

通常,函數(shù)語(yǔ)言會(huì)避開(kāi) while 循環(huán),因?yàn)?while 實(shí)現(xiàn)的大多數(shù)操作都可以使用遞歸來(lái)完成。函數(shù)語(yǔ)言真地非常類似于 遞歸。例如,可以考慮一下 “Scala by Example”(請(qǐng)參閱 參考資料)中展示的 quicksort 實(shí)現(xiàn),該實(shí)現(xiàn)可以與 Scala 實(shí)現(xiàn)一起使用:

清單 5. Quicksort(Java 版)

  1. //This is Java  
  2. void sort(int[] xs) {  
  3.   sort(xs, 0, xs.length -1 );  
  4. }  
  5. void sort(int[] xs, int l, int r) {  
  6.   int pivot = xs[(l+r)/2];  
  7.   int a = l; int b = r;  
  8.   while (a <= b)  
  9.     while (xs[a] < pivot) { a = a + 1; }  
  10.     while (xs[b] > pivot) { b = b – 1; }  
  11.     if (a <= b) {  
  12.       swap(xs, a, b);  
  13.       a = a + 1;  
  14.       b = b – 1;  
  15.     }  
  16.   }  
  17.   if (l < b) sort(xs, l, b);  
  18.   if (b < r) sort(xs, a, r);  
  19. }  
  20. void swap(int[] arr, int i, int j) {  
  21.   int t = arr[i]; arr[i] = arr[j]; arr[j] = t;  

不必深入太多的細(xì)節(jié),就可以了解 while 循環(huán)的用法,它是通過(guò)數(shù)組中的各種元素進(jìn)行迭代的,先找到一個(gè)支點(diǎn),然后依次對(duì)每個(gè)子元素進(jìn)行排序。毫不令人奇怪的是,while 循環(huán)也需要一組可變本地變量,在這里,這些變量被命名為 a 和 b,其中存儲(chǔ)的是當(dāng)前支點(diǎn)。注意,此版本甚至可以在循環(huán)自身中使用遞歸,兩次調(diào)用循環(huán)本身,一次用于對(duì)列表左手邊的內(nèi)容進(jìn)行排序,另一次對(duì)列表右手邊的內(nèi)容進(jìn)行排序。

這足以說(shuō)明清單 5 中的 quicksort 真的不太容易讀取,更不用說(shuō)理解它。現(xiàn)在來(lái)考慮一下 Scala 中的直接 等同物(這意味著該版本與上述版本盡量接近):

清單 6. Quicksort(Scala 版)

  1. //This is Scala  
  2. def sort(xs: Array[Int]) {  
  3.   def swap(i: Int, j: Int) {  
  4.     val t = xs(i); xs(i) = xs(j); xs(j) = t  
  5.   }  
  6.   def sort1(l: Int, r: Int) {  
  7.     val pivot = xs((l + r) / 2)  
  8.     var i = l; var j = r  
  9.     while (i <= j) {  
  10.       while (xs(i) < pivot) i += 1 
  11.       while (xs(j) > pivot) j -= 1 
  12.       if (i <= j) {  
  13.  swap(i, j)  
  14.  i += 1 
  15.  j -= 1 
  16.       }  
  17.     }  
  18.     if (l < j) sort1(l, j)  
  19.     if (j < r) sort1(i, r)  
  20.   }  
  21.   sort1(0, xs.length 1)  

清單 6 中的代碼看起來(lái)非常接近于 Java 版。也就是說(shuō),該代碼很長(zhǎng),很難看,并且難以理解(特別是并發(fā)性那一部分),明顯不具備 Java 版的一些優(yōu)點(diǎn)。

所以,我將其改進(jìn)……

清單 7. Quicksort(更好的 Scala 版)

  1. //This is Scala  
  2. def sort(xs: Array[Int]): Array[Int] =  
  3.   if (xs.length <= 1) xs  
  4.   else {  
  5.     val pivot = xs(xs.length / 2)  
  6.     Array.concat(  
  7.       sort(xs filter (pivot >)),  
  8.            xs filter (pivot ==),  
  9.       sort(xs filter (pivot <)))  
  10.   } 

顯然,清單 7 中的 Scala 代碼更簡(jiǎn)單一些。注意遞歸的使用,避免完全 while 循環(huán)。可以對(duì) Array 類型使用 filter 函數(shù),從而對(duì)其中的每個(gè)元素應(yīng)用 “greater-than”、“equals” 和 “l(fā)ess-than” 函數(shù)。事實(shí)上,在引導(dǎo)裝入程序之后,因?yàn)?if 表達(dá)式是返回某個(gè)值的表達(dá)式,所以從 sort() 返回的是 sort() 的定義中的(單個(gè))表達(dá)式。

簡(jiǎn)言之,我已經(jīng)將 while 循環(huán)的可變狀態(tài)完全再次分解為傳遞給各種 sort() 調(diào)用的參數(shù) —— 許多 Scala 狂熱愛(ài)好者認(rèn)為這是編寫 Scala 代碼的正確方式。

可能值得一提的是,Scala 本身并不介意您是否使用 while 代替迭代 —— 您會(huì)看到來(lái)自編譯器的 “您在干什么,在做蠢事嗎?” 的警告。Scala 也不會(huì)阻止您在可變狀態(tài)下編寫代碼。但是,使用 while 或可變狀態(tài)意味著犧牲 Scala 語(yǔ)言的另一個(gè)關(guān)鍵方面,即鼓勵(lì)編寫具有良好并行性的代碼。只要有可能并且可行,“Scala 式作風(fēng)” 會(huì)建議您優(yōu)先在命令塊上執(zhí)行遞歸。

#p#

編寫自己的語(yǔ)言結(jié)構(gòu)

我想走捷徑來(lái)討論一下 Scala 的控制結(jié)構(gòu),做一些大多數(shù) Java 開(kāi)發(fā)人員根本無(wú)法相信的事 —— 創(chuàng)建自己的語(yǔ)言結(jié)構(gòu)。

那些通過(guò)死讀書學(xué)習(xí)語(yǔ)言的書呆子會(huì)發(fā)現(xiàn)一件有趣的事:while 循環(huán)(Scala 中的一個(gè)原語(yǔ)結(jié)構(gòu))可能只是一個(gè)預(yù)定義函數(shù)。Scala 文檔以及假設(shè)的 “While” 定義中對(duì)此進(jìn)行了解釋說(shuō)明:

  1. // This is Scala  
  2. def While (p: => Boolean) (s: => Unit) {  
  3.   if (p) { s ; While(p)(s) }  
  4. }  

上述語(yǔ)句指定了一個(gè)表達(dá)式,該表達(dá)式產(chǎn)生了一個(gè)布爾值和一個(gè)不返回任何結(jié)果的代碼塊(Unit),這正是 while 所期望的。

擴(kuò)展這些代碼行很容易,并且可以根據(jù)需要使用它們,只需導(dǎo)入正確的庫(kù)即可。正如前面提到的,這是構(gòu)建語(yǔ)言的綜合方法。在下一節(jié)介紹 try 結(jié)構(gòu)的時(shí)候,請(qǐng)將這一點(diǎn)牢記于心。

再三嘗試

try 結(jié)構(gòu)允許編寫如下所示代碼:

清單 8. 如果最初沒(méi)有獲得成功……

  1. // This is Scala  
  2. val url =  
  3.   try {  
  4.     new URL(possibleURL)  
  5.   }  
  6.   catch {  
  7.     case ex: MalformedURLException =>  
  8.       new URL("www.tedneward.com")  
  9.   } 

清單 8 中的代碼與 清單 2 或 清單 3 中 if 示例中的代碼相差甚遠(yuǎn)。實(shí)際上,它比使用傳統(tǒng) Java 代碼編寫更具技巧,特別是在您想捕獲不可變位置上存儲(chǔ)的值的時(shí)候(正如我在 清單 4 中最后一個(gè)示例中所做的那樣)。這是 Scala 的函數(shù)特性的又一個(gè)優(yōu)點(diǎn)!

清單 8 中所示的 case ex: 語(yǔ)法是另一個(gè) Scala 結(jié)構(gòu)(匹配表達(dá)式)的一部分,該表達(dá)式用于 Scala 中的模式匹配。我們將研究模式匹配,這是函數(shù)語(yǔ)言的一個(gè)常見(jiàn)特性,稍后將介紹它;現(xiàn)在,只把它看作一個(gè)將用于 switch/case 的概念,那么哪種 C 風(fēng)格的 struct 將用于類呢?

現(xiàn)在,再來(lái)考慮一下異常處理。眾所周知,Scala 支持異常處理是因?yàn)樗且粋€(gè)表達(dá)式,但開(kāi)發(fā)人員想要的是處理異常的標(biāo)準(zhǔn)方法,并不僅僅是捕獲異常的能力。在 AspectJ 中,是通過(guò)創(chuàng)建方面(aspect)來(lái)實(shí)現(xiàn)這一點(diǎn)的,這些方面圍繞代碼部分進(jìn)行聯(lián)系,它們是通過(guò)切入點(diǎn)定義的,如果想讓數(shù)據(jù)庫(kù)的不同部分針對(duì)不同種類異常采取不同行為,那么必須小心編寫這些切入點(diǎn) —— SQLExceptions 的處理應(yīng)該不同于 IOExceptions 的處理,依此類推。

在 Scala 中,這只是微不足道的細(xì)節(jié)。請(qǐng)留神觀察!

清單 9. 一個(gè)自定義異常表達(dá)式

  1. // This is Scala  
  2. object Application  
  3. {  
  4.   def generateException()  
  5.   {  
  6.     System.out.println("Generating exception...");  
  7.     throw new Exception("Generated exception");  
  8.   }  
  9.  
  10.   def main(args : Array[String])  
  11.   {  
  12.     tryWithLogging  // This is not part of the language  
  13.     {  
  14.       generateException  
  15.     }  
  16.     System.out.println("Exiting main()");  
  17.   }  
  18.  
  19.   def tryWithLogging (s: => _) {  
  20.     try {  
  21.       s  
  22.     }  
  23.     catch {  
  24.       case ex: Exception =>  
  25.         // where would you like to log this?  
  26.  // I choose the console window, for now  
  27.  ex.printStackTrace()  
  28.     }  
  29.   }  
  30. }  

與前面討論過(guò)的 While 結(jié)構(gòu)類似,tryWithLogging 代碼只是來(lái)自某個(gè)庫(kù)的函數(shù)調(diào)用(在這里,是來(lái)自同一個(gè)類)??梢栽谶m當(dāng)?shù)牡胤绞褂貌煌闹黝}變量,不必編寫復(fù)雜的切入點(diǎn)代碼。

此方法的優(yōu)點(diǎn)在于它利用了 Scala 的捕獲一級(jí)結(jié)構(gòu)中橫切邏輯的功能 —— 以前只有面向方面的人才能對(duì)此進(jìn)行聲明。清單 9 中的一級(jí)結(jié)構(gòu)捕獲了一些異常(經(jīng)過(guò)檢查的和未經(jīng)檢查的都包括)并以特定方式進(jìn)行處理。上述想法的副作用非常多,惟一的限制也許就是想象力了。您只需記得 Scala 像許多函數(shù)語(yǔ)言一樣允許使用代碼塊(aka 函數(shù))作為參數(shù)并根據(jù)需要使用它們即可。

"for" 生成語(yǔ)言

所有這些都引導(dǎo)我們來(lái)到了 Scala 控制結(jié)構(gòu)套件的實(shí)際動(dòng)力源泉:for 結(jié)構(gòu)。該結(jié)構(gòu)看起來(lái)像是 Java 的增強(qiáng) for 循環(huán)的簡(jiǎn)單早期版,但它遠(yuǎn)比一般的 Java 編程人員開(kāi)始設(shè)想的更強(qiáng)大。

讓我們來(lái)看一下 Scala 如何處理集合上的簡(jiǎn)單順序迭代,根據(jù)您的 Java 編程經(jīng)驗(yàn),我想您應(yīng)該非常清楚該怎么做:

清單 10. 對(duì)一個(gè)對(duì)象使用 for 循環(huán)和對(duì)所有對(duì)象使用 for 循環(huán)

  1. // This is Scala  
  2. object Application  
  3. {  
  4.   def main(args : Array[String])  
  5.   {  
  6.     for (i <- 1 to 10// the left-arrow means "assignment" in Scala  
  7.       System.out.println("Counting " + i)  
  8.   }  

此代碼所做的正如您期望的那樣,循環(huán) 10 次,并且每次都輸出一些值。需要小心的是:表達(dá)式 “1 to 10” 并不意味著 Scala 內(nèi)置了整數(shù)感知(awareness of integer)以及從 1 到 10 的計(jì)數(shù)方式。從技術(shù)上說(shuō),這里存在一些更微妙的地方:編譯器使用 Int 類型上定義的方法 to 生成一個(gè) Range 對(duì)象(Scala 中的任何東西都是對(duì)象,還記得嗎?),該對(duì)象包含要迭代的元素。如果用 Scala 編譯器可以看見(jiàn)的方式重新編寫上述代碼,那么該代碼看起來(lái)很可能如下所示:

清單 11. 編譯器看見(jiàn)的內(nèi)容

  1. // This is Scala  
  2. object Application  
  3. {  
  4.   def main(args : Array[String])  
  5.   {  
  6.     for (i <- 1.to(10)) // the left-arrow means "assignment" in Scala  
  7.       System.out.println("Counting " + i)  
  8.   }  
  9. }  

實(shí)際上,Scala 的 for 并不了解那些成員,并且并不比其他任何對(duì)象類型做得更好。它所了解的是 scala.Iterable,scala.Iterable 定義了在集合上進(jìn)行迭代的基本行為。提供 Iterable 功能(從技術(shù)上說(shuō),它是 Scala 中的一個(gè)特征,但現(xiàn)在將它視為一個(gè)接口)的任何東西都可以用作 for 表達(dá)式的核心。List、Array,甚至是您自己的自定義類型,都可以在 for 中使用。

#p#

讓 Scala 與英語(yǔ)更接近

您可能已經(jīng)注意到,理解清單 11 中的 Scala 的 for 循環(huán)版本更容易一些。這要感謝 Range 對(duì)象暗中將兩端都包含在內(nèi),以下英語(yǔ)語(yǔ)言語(yǔ)法比 Java 語(yǔ)言更接近些。假如有一條 Range 語(yǔ)句說(shuō) “from 1 to 10, do this”,那么這意味著不再產(chǎn)生意外的 off-by-one 錯(cuò)誤。

特殊性

正如上面已經(jīng)證明的那樣,for 循環(huán)可以做許多事情,并不只是遍歷可迭代的項(xiàng)列表。事實(shí)上,可以使用一個(gè) for 循環(huán)在操作過(guò)程中過(guò)濾許多項(xiàng),并在每個(gè)階段都產(chǎn)生一個(gè)新列表:

清單 12. 看一看還有哪些優(yōu)點(diǎn)

  1. // This is Scala  
  2. object Application  
  3. {  
  4.   def main(args : Array[String])  
  5.   {  
  6.     for (i <- 1 to 10; i % 2 == 0)  
  7.       System.out.println("Counting " + i)  
  8.   }  

注意到清單 12 中 for 表達(dá)式的第二個(gè)子句了嗎?它是一個(gè)過(guò)濾器,實(shí)際上,只有那些傳遞給過(guò)濾器(即計(jì)算 true)的元素 “向前傳給” 了循環(huán)主體。在這里,只輸出了 1 到 10 的偶數(shù)數(shù)字。

并不要求 for 表達(dá)式的各個(gè)階段都成為過(guò)濾器。您甚至可以將一些完全平淡無(wú)奇的東西(從循環(huán)本身的觀點(diǎn)來(lái)看)放入管道中。例如以下代碼顯示了在下一個(gè)階段進(jìn)行計(jì)算之前的 i 的當(dāng)前值:

清單 13. 讓我如何愛(ài)上您呢?別那么冗長(zhǎng)

  1. // This is Scala  
  2. object App  
  3. {  
  4.   def log(item : _) : Boolean =  
  5.   {  
  6.     System.out.println("Evaluating " + item)  
  7.     true 
  8.   }  
  9.  
  10.   def main(args : Array[String]) =  
  11.   {  
  12.     for (val i <- 1 to 10; log(i); (i % 2) == 0)  
  13.       System.out.println("Counting " + i)  
  14.   }  
  15. }  

在運(yùn)行的時(shí)候,范圍 1 到 10 中的每個(gè)項(xiàng)都將發(fā)送給 log,它將通過(guò)顯式計(jì)算每個(gè)項(xiàng)是否為 true 來(lái) “批準(zhǔn)” 每個(gè)項(xiàng)。然后,for 的第三個(gè)子句將對(duì)這些項(xiàng)進(jìn)行篩選,過(guò)濾出那些滿足是偶數(shù)的條件的元素。因此,只將偶數(shù)傳遞給了循環(huán)主體本身。

簡(jiǎn)單性

在 Scala 中,可以將 Java 代碼中復(fù)雜的一長(zhǎng)串語(yǔ)句縮短為一個(gè)簡(jiǎn)單的表達(dá)式。例如,以下是遍歷目錄查找所有 .scala 文件并顯示每個(gè)文件名稱的方法:

清單 14. Finding .scala

  1. // This is Scala  
  2. object App  
  3. {  
  4.   def main(args : Array[String]) =  
  5.   {  
  6.     val filesHere = (new java.io.File(".")).listFiles  
  7.     for (  
  8.       file <- filesHere;  
  9.       if file.isFile;  
  10.       if file.getName.endsWith(".scala")  
  11.     ) System.out.println("Found " + file)  
  12.   }  

這種 for 過(guò)濾很常見(jiàn)(并且在此上下文中,分號(hào)很讓人討厭),使用這種過(guò)濾是為了幫助您做出忽略分號(hào)的決定。此外,Scala 允許將上述示例中的圓括號(hào)之間的語(yǔ)句直接作為代碼塊對(duì)待:

清單 15. Finding .scala(版本 2)

  1. // This is Scala  
  2. object App  
  3. {  
  4.   def main(args : Array[String]) =  
  5.   {  
  6.     val filesHere = (new java.io.File(".")).listFiles  
  7.     for {  
  8.       file <- filesHere  
  9.       if file.isFile  
  10.       if file.getName.endsWith(".scala")  
  11.     } System.out.println("Found " + file)  
  12.   }  
  13. }  

作為 Java 開(kāi)發(fā)人員,您可能發(fā)現(xiàn)最初的圓括號(hào)加分號(hào)的語(yǔ)法更直觀一些,沒(méi)有分號(hào)的曲線括號(hào)語(yǔ)法很難讀懂。幸運(yùn)的是,這兩種句法產(chǎn)生的代碼是等效的。

一些有趣的事

在 for 表達(dá)式的子句中可以分配一個(gè)以上的項(xiàng),如清單 16 中所示。

清單 16. 名稱中有什么?

  1. // This is Scala  
  2. object App  
  3. {  
  4.   def main(args : Array[String]) =  
  5.   {  
  6.     // Note the array-initialization syntax; the type (Array[String])  
  7.     // is inferred from the initialized elements  
  8.     val names = Array("Ted Neward""Neal Ford""Scott Davis",  
  9.       "Venkat Subramaniam""David Geary")  
  10.  
  11.     for {  
  12.       name <- names  
  13.       firstName = name.substring(0, name.indexOf(' '))  
  14.     } System.out.println("Found " + firstName)  
  15.   }  
  16. }  

這被稱為 “中途賦值(midstream assignment)”,其工作原理如下:定義了一個(gè)新值 firstName,該值用于保存每次執(zhí)行循環(huán)后的 substring 調(diào)用的值,以后可以在循環(huán)主體中使用此值。

這還引出了嵌套 迭代的概念,所有迭代都位于同一表達(dá)式中:

清單 17. Scala grep

  1. // This is Scala  
  2. object App  
  3. {  
  4.   def grep(pattern : String, dir : java.io.File) =  
  5.   {  
  6.     val filesHere = dir.listFiles  
  7.     for (  
  8.       file <- filesHere;  
  9.       if (file.getName.endsWith(".scala") || file.getName.endsWith(".java"));  
  10.       line <- scala.io.Source.fromFile(file).getLines;  
  11.       if line.trim.matches(pattern)  
  12.     ) println(line)  
  13.   }  
  14.  
  15.   def main(args : Array[String]) =  
  16.   {  
  17.     val pattern = ".*object.*" 
  18.       
  19.     grep pattern new java.io.File(".")  
  20.   }  
  21. }  

在此示例中,grep 內(nèi)部的 for 使用了兩個(gè)嵌套迭代,一個(gè)在指定目錄(其中每個(gè)文件都與 file 連接在一起)中找到的所有文件上進(jìn)行迭代,另一個(gè)迭代在目前正被迭代的文件(與 line 本地變量連接在一起)中發(fā)現(xiàn)的所有行上進(jìn)行迭代。

使用 Scala 的 for 結(jié)構(gòu)可以做更多的事,但目前為止提供的示例已足以表達(dá)我的觀點(diǎn):Scala 的 for 實(shí)際上是一條管道,它在將元素傳遞給循環(huán)主體之前處理元素組成的集合,每次一個(gè)。此管道其中的一部分負(fù)責(zé)將更多的元素添加到管道中(生成器),一部分負(fù)責(zé)編輯管道中的元素(過(guò)濾器),還有一些負(fù)責(zé)處理中間的操作(比如記錄)。無(wú)論如何,Scala 會(huì)帶給您與 Java 5 中引入的 “增強(qiáng)的 for 循環(huán)” 不同的體驗(yàn)。

匹配

今天要了解的最后一個(gè) Scala 控制結(jié)構(gòu)是 match,它提供了許多 Scala 模式匹配功能。幸運(yùn)的是,模式匹配會(huì)聲明對(duì)某個(gè)值進(jìn)行計(jì)算的代碼塊。首先,將執(zhí)行代碼塊中最接近的匹配結(jié)果。因此,在 Scala 中可以包含以下代碼:

清單 18. 一個(gè)簡(jiǎn)單的匹配

  1. // This is Scala  
  2. object App  
  3. {  
  4.   def main(args : Array[String]) =  
  5.   {  
  6.     for (arg <- args)  
  7.       arg match {  
  8.  case "Java" => println("Java is nice...")  
  9.  case "Scala" => println("Scala is cool...")  
  10.  case "Ruby" => println("Ruby is for wimps...")  
  11.  case _ => println("What are you, a VB programmer?")  
  12.       }  
  13.   }  

剛開(kāi)始您可能將 Scala 模式匹配設(shè)想為支持 String 的 “開(kāi)關(guān)’,帶有通常用作通配符的下劃線字符,而這正是典型開(kāi)關(guān)中的默認(rèn)情況。但是,這樣想會(huì)極大地低估該語(yǔ)言。模式匹配是許多(但不是大多數(shù))函數(shù)語(yǔ)言中可以找到的另一個(gè)特性,它提供了一些有用的功能。

對(duì)于初學(xué)者(盡管這沒(méi)什么好奇怪的),可能認(rèn)為 match 表達(dá)式自身會(huì)產(chǎn)生一個(gè)值,該值可能出現(xiàn)在賦值語(yǔ)句的右邊,正如 if 和 try 語(yǔ)句所做的那樣。這一點(diǎn)本身也很有用,但匹配的真正威力體現(xiàn)在基于各種類型進(jìn)行匹配時(shí),而不是如上所述匹配單個(gè)類型的值,或者更多的時(shí)候,它是兩種匹配的組合。

因此,假設(shè)您有一個(gè)聲明返回 Object 的函數(shù)或方法 —— 在這里,Java 的 java.lang.reflect.Method.invoke() 方法的結(jié)果可能是一個(gè)好例子。通常,在使用 Java 語(yǔ)言計(jì)算結(jié)果時(shí),首先應(yīng)該確定其類型;但在 Scala 中,可以使用模式匹配簡(jiǎn)化該操作:

清單 19. 您是什么?

  1. //This is Scala  
  2. object App  
  3. {  
  4.   def main(args : Array[String]) =  
  5.   {  
  6.     // The Any type is exactly what it sounds like: a kind of wildcard that  
  7.     // accepts any type  
  8.     def describe(x: Any) = x match {   
  9.       case 5 => "five"   
  10.       case true => "truth"   
  11.       case "hello" => "hi!"   
  12.       case Nil => "the empty list"   
  13.       case _ => "something else"   
  14.     }  
  15.       
  16.     println describe(5)  
  17.     println describe("hello")  
  18.   }  

因?yàn)?match 的很容易簡(jiǎn)單明了地描述如何針對(duì)各種值和類型進(jìn)行匹配的能力,模式匹配常用于解析器和解釋器中,在那里,解析流中的當(dāng)前標(biāo)記是與一系列可能的匹配子句匹配的。然后,將針對(duì)另一系列子句應(yīng)用下一個(gè)標(biāo)記,依此類推(注意,這也是使用函數(shù)語(yǔ)言編寫許多語(yǔ)言解析器、編譯器和其他與代碼有關(guān)的工具的部分原因,這些函數(shù)語(yǔ)言中包括 Haskell 或 ML)。

關(guān)于模式匹配,還有許多可說(shuō)的東西,但這些會(huì)將我們直接引導(dǎo)至 Scala 的另一個(gè)特性 case 類,我想將它留到下次再介紹。

結(jié)束語(yǔ)

Scala 在許多方面看起來(lái)都非常類似于 Java,但實(shí)際上只有 for 結(jié)構(gòu)存在一些相似性。核心語(yǔ)法元素的函數(shù)特性不僅提供了一些有用的特性(比如已經(jīng)提到的賦值功能),還提供了使用新穎有趣的方式擴(kuò)展語(yǔ)言的能力,不必修改核心 javac 編譯器本身。這使該語(yǔ)言更加符合 DSL 的定義(這些 DSL 是在現(xiàn)有語(yǔ)言的語(yǔ)法中定義的),并且更加符合編程人員根據(jù)一組核心原語(yǔ)(a la Lisp 或 Scheme)構(gòu)建抽象的愿望。

關(guān)于 Scala,有如此多的內(nèi)容可以談?wù)?,但我們這個(gè)月的時(shí)間已經(jīng)用完了。記得試用最新的 Scala bits(在撰寫本文時(shí)是 2.7.0-final)并嘗試提供的示例,感受一下該語(yǔ)言的操作(請(qǐng)參閱 參考資料)。請(qǐng)記住,到下一次的時(shí)候,Scala 會(huì)將一些有趣的(函數(shù))特性放入編程中!

【相關(guān)閱讀】

  1. Scala編程語(yǔ)言專題
  2. 面向Java開(kāi)發(fā)人員的Scala指南:理解Scala的類語(yǔ)法和語(yǔ)義
  3. 面向Java開(kāi)發(fā)人員的Scala指南:面向?qū)ο蟮暮瘮?shù)編程
  4. Scala的類型系統(tǒng):取代復(fù)雜的通配符
  5. Scala的類型系統(tǒng) 比Java更靈活
責(zé)任編輯:yangsai 來(lái)源: IBMDW
相關(guān)推薦

2009-09-28 11:01:39

從Java走進(jìn)Scal

2009-08-21 16:17:25

ScalaTwitter API

2009-07-15 10:14:25

Scala并發(fā)性

2009-12-09 09:15:47

從Java走進(jìn)ScalTwitter API

2009-02-04 17:32:03

ibmdwJavaScala

2009-06-16 17:54:38

Scala類語(yǔ)法語(yǔ)義

2009-10-14 11:14:38

ScitterScalaTwitter

2009-08-14 11:35:01

Scala Actor

2009-06-17 13:57:25

Scala元組數(shù)組

2009-06-16 17:09:17

Scala面向?qū)ο?/a>函數(shù)編程

2009-06-19 10:51:39

Scalapackage訪問(wèn)修飾符

2009-07-22 07:49:00

Scala控制結(jié)構(gòu)

2009-06-17 13:26:06

scala繼承模型

2009-06-19 11:13:47

Scalacase類模式匹配

2009-06-19 11:42:09

Scala計(jì)算器解析

2009-09-09 10:50:55

Scala例子Scala與Java

2009-07-08 12:43:59

Scala ServlScala語(yǔ)言

2009-09-28 10:38:57

Scala教程

2010-09-14 15:34:41

Scala

2009-06-19 13:16:36

Scala計(jì)算器解析器組合子
點(diǎn)贊
收藏

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