日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
從Java走進(jìn)Scala:Scala控制結(jié)構(gòu)內(nèi)部揭密

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

創(chuàng)新互聯(lián)專(zhuān)注于企業(yè)成都營(yíng)銷(xiāo)網(wǎng)站建設(shè)、網(wǎng)站重做改版、石門(mén)網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5高端網(wǎng)站建設(shè)、商城網(wǎng)站制作、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性?xún)r(jià)比高,為石門(mén)等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。

例如,對(duì)控制結(jié)構(gòu)(比如 if、while 和 for)使用 Scala 的方法。盡管這些控制結(jié)構(gòu)看起來(lái)類(lèi)似一些老的、還比較不錯(cuò)的 Java 結(jié)構(gòu),但實(shí)際上 Scala 為它們?cè)黾恿艘恍┩耆煌奶匦?。本月的文章是關(guān)于使用 Scala 控制結(jié)構(gòu)時(shí)能夠期望獲得哪些東西的入門(mén)級(jí)讀物,而不是在制造許多錯(cuò)誤(并編寫(xiě)一堆錯(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. @scala.reflect.BeanProperty
  6. var lastName = ln
  7. @scala.reflect.BeanProperty
  8. var age = a
  9. override def toString =
  10.     "[Person firstName:" + firstName + " lastName:" + lastName +
  11.  " age:" + age + " ]"
  12.    }
  13.     

清單 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ù)。類(lèi)似于任何庫(kù),此功能可以替換、擴(kuò)充或擴(kuò)展。

根據(jù)一組核心原語(yǔ)構(gòu)建語(yǔ)言的合成 特性由來(lái)已久,可以追溯到 20 世紀(jì) 60 年代和 70 年代使用 Smalltalk、Lisp 和 Scheme 的時(shí)候。諸如 Lisp 和 Scheme 之類(lèi)的語(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 代碼塊中值種類(lèi)相同的形式呈現(xiàn),并且必須產(chǎn)生同一種類(lèi)的值。這意味著不論以何種方式執(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á)式,所以重寫(xiě)上述代碼會(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ò)編寫(xiě)代碼將結(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ì)不以為然并且詢(xún)問(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ǔ)言真地非常類(lèi)似于 遞歸。例如,可以考慮一下 “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;
  22. }

不必深入太多的細(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ō)理解它?,F(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)
  22. }

清單 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 類(lèi)型使用 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)為這是編寫(xiě) Scala 代碼的正確方式。

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

#p#

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

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

那些通過(guò)死讀書(shū)學(xué)習(xí)語(yǔ)言的書(shū)呆子會(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)允許編寫(xiě)如下所示代碼:

清單 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 代碼編寫(xiě)更具技巧,特別是在您想捕獲不可變位置上存儲(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 將用于類(lèi)呢?

現(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ì)不同種類(lèi)異常采取不同行為,那么必須小心編寫(xiě)這些切入點(diǎn) —— SQLExceptions 的處理應(yīng)該不同于 IOExceptions 的處理,依此類(lèi)推。

在 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.   def main(args : Array[String])
  10.   {
  11.     tryWithLogging  // This is not part of the language
  12.     {
  13.       generateException
  14.     }
  15.     System.out.println("Exiting main()");
  16.   }
  17.   def tryWithLogging (s: => _) {
  18.     try {
  19.       s
  20.     }
  21.     catch {
  22.       case ex: Exception =>
  23.         // where would you like to log this?
  24.  // I choose the console window, for now
  25.  ex.printStackTrace()
  26.     }
  27.   }
  28. }

與前面討論過(guò)的 While 結(jié)構(gòu)類(lèi)似,tryWithLogging 代碼只是來(lái)自某個(gè)庫(kù)的函數(shù)調(diào)用(在這里,是來(lái)自同一個(gè)類(lèi))??梢栽谶m當(dāng)?shù)牡胤绞褂貌煌闹黝}變量,不必編寫(xiě)復(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.   }
  9. }

此代碼所做的正如您期望的那樣,循環(huán) 10 次,并且每次都輸出一些值。需要小心的是:表達(dá)式 “1 to 10” 并不意味著 Scala 內(nèi)置了整數(shù)感知(awareness of integer)以及從 1 到 10 的計(jì)數(shù)方式。從技術(shù)上說(shuō),這里存在一些更微妙的地方:編譯器使用 Int 類(lèi)型上定義的方法 to 生成一個(gè) Range 對(duì)象(Scala 中的任何東西都是對(duì)象,還記得嗎?),該對(duì)象包含要迭代的元素。如果用 Scala 編譯器可以看見(jiàn)的方式重新編寫(xiě)上述代碼,那么該代碼看起來(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ì)象類(lèi)型做得更好。它所了解的是 scala.Iterable,scala.Iterable 定義了在集合上進(jìn)行迭代的基本行為。提供 Iterable 功能(從技術(shù)上說(shuō),它是 Scala 中的一個(gè)特征,但現(xiàn)在將它視為一個(gè)接口)的任何東西都可以用作 for 表達(dá)式的核心。List、Array,甚至是您自己的自定義類(lèi)型,都可以在 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.   }
  9. }

注意到清單 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.   def main(args : Array[String]) =
  10.   {
  11.     for (val i <- 1 to 10; log(i); (i % 2) == 0)
  12.       System.out.println("Counting " + i)
  13.   }
  14. }

在運(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ò)濾出那些滿(mǎn)足是偶數(shù)的條件的元素。因此,只將偶數(shù)傳遞給了循環(huán)主體本身。

簡(jiǎn)單性

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

清單 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.   }
  13. }

這種 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. 名稱(chēng)中有什么?

 
 
 
  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.     for {
  11.       name <- names
  12.       firstName = name.substring(0, name.indexOf(' '))
  13.     } System.out.println("Found " + firstName)
  14.   }
  15. }

這被稱(chēng)為 “中途賦值(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.   def main(args : Array[String]) =
  15.   {
  16.     val pattern = ".*object.*"
  17.     
  18.     grep pattern new java.io.File(".")
  19.   }
  20. }

在此示例中,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.   }
  14. }

剛開(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)在基于各種類(lèi)型進(jìn)行匹配時(shí),而不是如上所述匹配單個(gè)類(lèi)型的值,或者更多的時(shí)候,它是兩種匹配的組合。

因此,假設(shè)您有一個(gè)聲明返回 Object 的函數(shù)或方法 —— 在這里,Java 的 java.lang.reflect.Method.invoke() 方法的結(jié)果可能是一個(gè)好例子。通常,在使用 Java 語(yǔ)言計(jì)算結(jié)果時(shí),首先應(yīng)該確定其類(lèi)型;但在 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.   }
  19. }

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

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

結(jié)束語(yǔ)

Scala 在許多方面看起來(lái)都非常類(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(在撰寫(xiě)本文時(shí)是 2.7.0-final)并嘗試提供的示例,感受一下該語(yǔ)言的操作(請(qǐng)參閱 參考資料)。請(qǐng)記住,到下一次的時(shí)候,Scala 會(huì)將一些有趣的(函數(shù))特性放入編程中!

【相關(guān)閱讀】

  1. Scala編程語(yǔ)言專(zhuān)題
  2. 面向Java開(kāi)發(fā)人員的Scala指南:理解Scala的類(lèi)語(yǔ)法和語(yǔ)義
  3. 面向Java開(kāi)發(fā)人員的Scala指南:面向?qū)ο蟮暮瘮?shù)編程
  4. Scala的類(lèi)型系統(tǒng):取代復(fù)雜的通配符
  5. Scala的類(lèi)型系統(tǒng) 比Java更靈活

分享名稱(chēng):從Java走進(jìn)Scala:Scala控制結(jié)構(gòu)內(nèi)部揭密
URL標(biāo)題:http://www.5511xx.com/article/cdsjjoo.html