從Java走進Scala:Scala控制結(jié)構(gòu)內(nèi)部揭密
迄今為止,在此系列中,我們已經(jīng)討論了 Scala 對生態(tài)環(huán)境的保真度,展示了 Scala 如何將眾多的 Java 核心對象功能合并在一起。如果 Scala 只是編寫對象的另一種方式,那么它不會有任何引人注意的地方,或者說不再那么功能強大。Scala 的函數(shù)概念和對象概念的合并,以及它對編程人員效率的重視,這些使得學習 Scala 語言比 Java-cum-Scala 編程人員所想象的體驗更加復雜、更加微妙。
例如,對控制結(jié)構(gòu)(比如 if、while 和 for)使用 Scala 的方法。盡管這些控制結(jié)構(gòu)看起來類似一些老的、還比較不錯的 Java 結(jié)構(gòu),但實際上 Scala 為它們增加了一些完全不同的特性。本月的文章是關(guān)于使用 Scala 控制結(jié)構(gòu)時能夠期望獲得哪些東西的入門級讀物,而不是在制造許多錯誤(并編寫一堆錯誤代碼)之后,讓您冒著遭受挫折的風險去尋找差異。
修訂后的 Person.scala
在 本系列的上一篇文章 中,可以了解到 Scala 能夠通過定義一些方法來定義 POJO,這些方法模仿基于 POJO 的環(huán)境所需的傳統(tǒng) “getter 和 setter”。在這篇文章發(fā)表之后,我收到了 Bill Venners 發(fā)來的電子郵件,Bill Venners 是即將發(fā)表的正式的 Scala 參考資料使用 Scala 編程(請參閱 參考資料)的合著者之一。Bill 指出了實現(xiàn)上述操作的一個更簡單的方法,即使用 scala.reflect.BeanProperty 標注,如下所示:
清單 1. 修改后的 Person.scala
- class Person(fn:String, ln:String, a:Int)
- {
- @scala.reflect.BeanProperty
- var firstName = fn
- @scala.reflect.BeanProperty
- var lastName = ln
- @scala.reflect.BeanProperty
- var age = a
- override def toString =
- "[Person firstName:" + firstName + " lastName:" + lastName +
- " age:" + age + " ]"
- }
清單 1 中的方法(上一篇文章 中的清單 13 的修訂版)為指定的 var 生成了 get/set 方法對。惟一的缺陷是這些方法并不實際存在于 Scala 代碼中,因此其他 Scala 代碼無法調(diào)用它們。這通常不是什么大問題,因為 Scala 將對為自己生成的字段使用已生成的方法;如果事先不知道,那么這些對您而言可能是一個驚喜。
在查看了清單 1 中的代碼之后,最讓我感到震動的是,Scala 并沒有只演示組合函數(shù)概念和對象概念的強大威力,它還演示了自 Java ***發(fā)布之后的 30 年里對象語言帶來的一些益處。
控制是一種幻想
您將看到的許多奇怪的、不可思議的東西都可以歸功于 Scala 的函數(shù)特性,因此,簡單介紹一下函數(shù)語言開發(fā)和演變的背景可能非常有用。
在函數(shù)語言中,將越來越高級的結(jié)構(gòu)直接構(gòu)建到語言中是不常見的。此外,語言是通過一組核心原語結(jié)構(gòu)定義的。在與將函數(shù)作為對象傳遞的功能結(jié)合之后,可用來定義功能的高階函數(shù) 看起來 像是超出了核心語言的范圍,但實際上它只是一個庫。類似于任何庫,此功能可以替換、擴充或擴展。
根據(jù)一組核心原語構(gòu)建語言的合成 特性由來已久,可以追溯到 20 世紀 60 年代和 70 年代使用 Smalltalk、Lisp 和 Scheme 的時候。諸如 Lisp 和 Scheme 之類的語言因為它們在更低級別的抽象上定義更高級別抽象的能力而受到人們的狂熱追捧。編程人員可以使用高級抽象,用它們構(gòu)建更高級的抽象。如今聽到討論這個過程時,它通常是關(guān)于特定于域的語言(或 DSL)的(請參閱 參考資料)。實際上,它只是關(guān)于如何在抽象之上構(gòu)建抽象的過程。
在 Java 語言中,惟一選擇就是利用 API 調(diào)用完成此操作;在 Scala 中,可以通過擴展語言本身實現(xiàn)它。試圖擴展 Java 語言會帶來創(chuàng)建極端場景(corner case)的風險,這些場景將威脅全局的穩(wěn)定性。而試圖擴展 Scala 則只意味著創(chuàng)建一個新庫。
#p#
If 結(jié)構(gòu)
我們將從傳統(tǒng)的 if 結(jié)構(gòu)開始 —— 當然,此結(jié)構(gòu)必須是最容易處理的結(jié)構(gòu)之一,不是嗎?畢竟,從理論上說,if 只檢查一個條件。如果條件為真,則執(zhí)行后面跟著的代碼。
但是,這種簡單性可能帶有欺騙性。傳統(tǒng)上,Java 語言對 if 的 else 子句的使用是隨意的,并且假定如果條件出錯,可以只跳過代碼塊。但在函數(shù)語句中,情況不是這樣。為了保持函數(shù)語句的算術(shù)特性,所有一切都必須以表達式計算的方式出現(xiàn),包括 if 子句本身(對于 Java 開發(fā)人員,這正是三元操作符 —— ?: 表達式 —— 的工作方式)。
在 Scala 中,非真代碼塊(代碼塊的 else 部分)必須以與 if 代碼塊中值種類相同的形式呈現(xiàn),并且必須產(chǎn)生同一種類的值。這意味著不論以何種方式執(zhí)行代碼,總會產(chǎn)生一個值。例如,請參見以下 Java 代碼:
清單 2. 哪個配置文件?(Java 版)
- // This is Java
- String filename = "default.properties";
- if (options.contains("configFile"))
- filename = (String)options.get("configFile");
因為 Scala 中的 if 結(jié)構(gòu)自身就是一個表達式,所以重寫上述代碼會使它們成為清單 3 中所示的更正確的代碼片段:
清單 3. 哪個配置文件?(Scala 版)
- // This is Scala
- val filename =
- if (options.contains("configFile"))
- options.get("configFile")
- else
- "default.properties"
也就是說,Scala 編程人員通常應(yīng)該*** val 結(jié)構(gòu),并在明確需要可變性的時候選擇 var。原因很簡單:除了使編程更容易之外,val 還能確保程序的線程安全性,Scala 中的一個內(nèi)在主題是:幾乎每次認為需要可變狀態(tài)時,其實都不需要可變狀態(tài)。讓我們從不可變字段和本地變量(val)開始,這是展示上述情況的一種方法,甚至對最堅定的 Java 懷疑論者也是如此。從 Java 中的 final 開始介紹可能不是很合理,或許是因為 Java 的非函數(shù)特性,盡管此原因不可取。一些好奇的 Java 開發(fā)人員可能想嘗試一下。
盡管真正的贏家是 Scala,但可以通過編寫代碼將結(jié)果分配給 val,而不是 var。在設(shè)置之后,就無法對 val 進行更改,這與 Java 語言中 final 變量的操作方式是相同的。不可變本地變量最顯著的副作用是很容易實現(xiàn)并發(fā)性。試圖用 Java 代碼實現(xiàn)同樣的操作時,會帶來許多不錯的、易讀的好代碼,如清單 4 中所示:
清單 4. 哪個配置文件?(Java 版,三元式)
- //This is Java
- final String filename =
- options.contains("configFile") ?
- options.get("configFile") : "default.properties";
用代碼評審解釋這一點可能需要點技巧。也許這樣做是正確的,但許多 Java 編程人員會不以為然并且詢問 “您做那個干什么”?
val 與 var
您可能想更多地了解 val 與 var 之間的不同,實際上,它們的不同之處在于 —— 一個是只讀的值,另一個是可變的變量。通常,函數(shù)語言,特別是被認為是 “純” 函數(shù)語言(不允許帶有副作用,比如可變狀態(tài))的那些函數(shù)語言,只支持 val 概念;但是,因為 Scala 要同時吸引函數(shù)編程人員和命令/對象編程人員,所以這二種結(jié)構(gòu)它都提供。
已公開的 while 結(jié)構(gòu)
接下來,讓我們來看一下 while 及其同胞 do-while。它們做的基本上是同一件事:測試一個條件,如果該條件為真,則繼續(xù)執(zhí)行提供的代碼塊。
通常,函數(shù)語言會避開 while 循環(huán),因為 while 實現(xiàn)的大多數(shù)操作都可以使用遞歸來完成。函數(shù)語言真地非常類似于 遞歸。例如,可以考慮一下 “Scala by Example”(請參閱 參考資料)中展示的 quicksort 實現(xiàn),該實現(xiàn)可以與 Scala 實現(xiàn)一起使用:
清單 5. Quicksort(Java 版)
- //This is Java
- void sort(int[] xs) {
- sort(xs, 0, xs.length -1 );
- }
- void sort(int[] xs, int l, int r) {
- int pivot = xs[(l+r)/2];
- int a = l; int b = r;
- while (a <= b)
- while (xs[a] < pivot) { a = a + 1; }
- while (xs[b] > pivot) { b = b – 1; }
- if (a <= b) {
- swap(xs, a, b);
- a = a + 1;
- b = b – 1;
- }
- }
- if (l < b) sort(xs, l, b);
- if (b < r) sort(xs, a, r);
- }
- void swap(int[] arr, int i, int j) {
- int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
- }
不必深入太多的細節(jié),就可以了解 while 循環(huán)的用法,它是通過數(shù)組中的各種元素進行迭代的,先找到一個支點,然后依次對每個子元素進行排序。毫不令人奇怪的是,while 循環(huán)也需要一組可變本地變量,在這里,這些變量被命名為 a 和 b,其中存儲的是當前支點。注意,此版本甚至可以在循環(huán)自身中使用遞歸,兩次調(diào)用循環(huán)本身,一次用于對列表左手邊的內(nèi)容進行排序,另一次對列表右手邊的內(nèi)容進行排序。
這足以說明清單 5 中的 quicksort 真的不太容易讀取,更不用說理解它。現(xiàn)在來考慮一下 Scala 中的直接 等同物(這意味著該版本與上述版本盡量接近):
清單 6. Quicksort(Scala 版)
- //This is Scala
- def sort(xs: Array[Int]) {
- def swap(i: Int, j: Int) {
- val t = xs(i); xs(i) = xs(j); xs(j) = t
- }
- def sort1(l: Int, r: Int) {
- val pivot = xs((l + r) / 2)
- var i = l; var j = r
- while (i <= j) {
- while (xs(i) < pivot) i += 1
- while (xs(j) > pivot) j -= 1
- if (i <= j) {
- swap(i, j)
- i += 1
- j -= 1
- }
- }
- if (l < j) sort1(l, j)
- if (j < r) sort1(i, r)
- }
- sort1(0, xs.length 1)
- }
清單 6 中的代碼看起來非常接近于 Java 版。也就是說,該代碼很長,很難看,并且難以理解(特別是并發(fā)性那一部分),明顯不具備 Java 版的一些優(yōu)點。
所以,我將其改進……
清單 7. Quicksort(更好的 Scala 版)
- //This is Scala
- def sort(xs: Array[Int]): Array[Int] =
- if (xs.length <= 1) xs
- else {
- val pivot = xs(xs.length / 2)
- Array.concat(
- sort(xs filter (pivot >)),
- xs filter (pivot ==),
- sort(xs filter (pivot <)))
- }
顯然,清單 7 中的 Scala 代碼更簡單一些。注意遞歸的使用,避免完全 while 循環(huán)??梢詫?Array 類型使用 filter 函數(shù),從而對其中的每個元素應(yīng)用 “greater-than”、“equals” 和 “l(fā)ess-than” 函數(shù)。事實上,在引導裝入程序之后,因為 if 表達式是返回某個值的表達式,所以從 sort() 返回的是 sort() 的定義中的(單個)表達式。
簡言之,我已經(jīng)將 while 循環(huán)的可變狀態(tài)完全再次分解為傳遞給各種 sort() 調(diào)用的參數(shù) —— 許多 Scala 狂熱愛好者認為這是編寫 Scala 代碼的正確方式。
可能值得一提的是,Scala 本身并不介意您是否使用 while 代替迭代 —— 您會看到來自編譯器的 “您在干什么,在做蠢事嗎?” 的警告。Scala 也不會阻止您在可變狀態(tài)下編寫代碼。但是,使用 while 或可變狀態(tài)意味著犧牲 Scala 語言的另一個關(guān)鍵方面,即鼓勵編寫具有良好并行性的代碼。只要有可能并且可行,“Scala 式作風” 會建議您優(yōu)先在命令塊上執(zhí)行遞歸。
#p#
編寫自己的語言結(jié)構(gòu)
我想走捷徑來討論一下 Scala 的控制結(jié)構(gòu),做一些大多數(shù) Java 開發(fā)人員根本無法相信的事 —— 創(chuàng)建自己的語言結(jié)構(gòu)。
那些通過死讀書學習語言的書呆子會發(fā)現(xiàn)一件有趣的事:while 循環(huán)(Scala 中的一個原語結(jié)構(gòu))可能只是一個預定義函數(shù)。Scala 文檔以及假設(shè)的 “While” 定義中對此進行了解釋說明:
- // This is Scala
- def While (p: => Boolean) (s: => Unit) {
- if (p) { s ; While(p)(s) }
- }
上述語句指定了一個表達式,該表達式產(chǎn)生了一個布爾值和一個不返回任何結(jié)果的代碼塊(Unit),這正是 while 所期望的。
擴展這些代碼行很容易,并且可以根據(jù)需要使用它們,只需導入正確的庫即可。正如前面提到的,這是構(gòu)建語言的綜合方法。在下一節(jié)介紹 try 結(jié)構(gòu)的時候,請將這一點牢記于心。
再三嘗試
try 結(jié)構(gòu)允許編寫如下所示代碼:
清單 8. 如果最初沒有獲得成功……
- // This is Scala
- val url =
- try {
- new URL(possibleURL)
- }
- catch {
- case ex: MalformedURLException =>
- new URL("www.tedneward.com")
- }
清單 8 中的代碼與 清單 2 或 清單 3 中 if 示例中的代碼相差甚遠。實際上,它比使用傳統(tǒng) Java 代碼編寫更具技巧,特別是在您想捕獲不可變位置上存儲的值的時候(正如我在 清單 4 中最后一個示例中所做的那樣)。這是 Scala 的函數(shù)特性的又一個優(yōu)點!
清單 8 中所示的 case ex: 語法是另一個 Scala 結(jié)構(gòu)(匹配表達式)的一部分,該表達式用于 Scala 中的模式匹配。我們將研究模式匹配,這是函數(shù)語言的一個常見特性,稍后將介紹它;現(xiàn)在,只把它看作一個將用于 switch/case 的概念,那么哪種 C 風格的 struct 將用于類呢?
現(xiàn)在,再來考慮一下異常處理。眾所周知,Scala 支持異常處理是因為它是一個表達式,但開發(fā)人員想要的是處理異常的標準方法,并不僅僅是捕獲異常的能力。在 AspectJ 中,是通過創(chuàng)建方面(aspect)來實現(xiàn)這一點的,這些方面圍繞代碼部分進行聯(lián)系,它們是通過切入點定義的,如果想讓數(shù)據(jù)庫的不同部分針對不同種類異常采取不同行為,那么必須小心編寫這些切入點 —— SQLExceptions 的處理應(yīng)該不同于 IOExceptions 的處理,依此類推。
在 Scala 中,這只是微不足道的細節(jié)。請留神觀察!
清單 9. 一個自定義異常表達式
- // This is Scala
- object Application
- {
- def generateException()
- {
- System.out.println("Generating exception...");
- throw new Exception("Generated exception");
- }
- def main(args : Array[String])
- {
- tryWithLogging // This is not part of the language
- {
- generateException
- }
- System.out.println("Exiting main()");
- }
- def tryWithLogging (s: => _) {
- try {
- s
- }
- catch {
- case ex: Exception =>
- // where would you like to log this?
- // I choose the console window, for now
- ex.printStackTrace()
- }
- }
- }
與前面討論過的 While 結(jié)構(gòu)類似,tryWithLogging 代碼只是來自某個庫的函數(shù)調(diào)用(在這里,是來自同一個類)。可以在適當?shù)牡胤绞褂貌煌闹黝}變量,不必編寫復雜的切入點代碼。
此方法的優(yōu)點在于它利用了 Scala 的捕獲一級結(jié)構(gòu)中橫切邏輯的功能 —— 以前只有面向方面的人才能對此進行聲明。清單 9 中的一級結(jié)構(gòu)捕獲了一些異常(經(jīng)過檢查的和未經(jīng)檢查的都包括)并以特定方式進行處理。上述想法的副作用非常多,惟一的限制也許就是想象力了。您只需記得 Scala 像許多函數(shù)語言一樣允許使用代碼塊(aka 函數(shù))作為參數(shù)并根據(jù)需要使用它們即可。
"for" 生成語言
所有這些都引導我們來到了 Scala 控制結(jié)構(gòu)套件的實際動力源泉:for 結(jié)構(gòu)。該結(jié)構(gòu)看起來像是 Java 的增強 for 循環(huán)的簡單早期版,但它遠比一般的 Java 編程人員開始設(shè)想的更強大。
讓我們來看一下 Scala 如何處理集合上的簡單順序迭代,根據(jù)您的 Java 編程經(jīng)驗,我想您應(yīng)該非常清楚該怎么做:
清單 10. 對一個對象使用 for 循環(huán)和對所有對象使用 for 循環(huán)
- // This is Scala
- object Application
- {
- def main(args : Array[String])
- {
- for (i <- 1 to 10) // the left-arrow means "assignment" in Scala
- System.out.println("Counting " + i)
- }
- }
此代碼所做的正如您期望的那樣,循環(huán) 10 次,并且每次都輸出一些值。需要小心的是:表達式 “1 to 10” 并不意味著 Scala 內(nèi)置了整數(shù)感知(awareness of integer)以及從 1 到 10 的計數(shù)方式。從技術(shù)上說,這里存在一些更微妙的地方:編譯器使用 Int 類型上定義的方法 to 生成一個 Range 對象(Scala 中的任何東西都是對象,還記得嗎?),該對象包含要迭代的元素。如果用 Scala 編譯器可以看見的方式重新編寫上述代碼,那么該代碼看起來很可能如下所示:
清單 11. 編譯器看見的內(nèi)容
- // This is Scala
- object Application
- {
- def main(args : Array[String])
- {
- for (i <- 1.to(10)) // the left-arrow means "assignment" in Scala
- System.out.println("Counting " + i)
- }
- }
實際上,Scala 的 for 并不了解那些成員,并且并不比其他任何對象類型做得更好。它所了解的是 scala.Iterable,scala.Iterable 定義了在集合上進行迭代的基本行為。提供 Iterable 功能(從技術(shù)上說,它是 Scala 中的一個特征,但現(xiàn)在將它視為一個接口)的任何東西都可以用作 for 表達式的核心。List、Array,甚至是您自己的自定義類型,都可以在 for 中使用。
#p#
讓 Scala 與英語更接近
您可能已經(jīng)注意到,理解清單 11 中的 Scala 的 for 循環(huán)版本更容易一些。這要感謝 Range 對象暗中將兩端都包含在內(nèi),以下英語語言語法比 Java 語言更接近些。假如有一條 Range 語句說 “from 1 to 10, do this”,那么這意味著不再產(chǎn)生意外的 off-by-one 錯誤。
特殊性
正如上面已經(jīng)證明的那樣,for 循環(huán)可以做許多事情,并不只是遍歷可迭代的項列表。事實上,可以使用一個 for 循環(huán)在操作過程中過濾許多項,并在每個階段都產(chǎn)生一個新列表:
清單 12. 看一看還有哪些優(yōu)點
- // This is Scala
- object Application
- {
- def main(args : Array[String])
- {
- for (i <- 1 to 10; i % 2 == 0)
- System.out.println("Counting " + i)
- }
- }
注意到清單 12 中 for 表達式的第二個子句了嗎?它是一個過濾器,實際上,只有那些傳遞給過濾器(即計算 true)的元素 “向前傳給” 了循環(huán)主體。在這里,只輸出了 1 到 10 的偶數(shù)數(shù)字。
并不要求 for 表達式的各個階段都成為過濾器。您甚至可以將一些完全平淡無奇的東西(從循環(huán)本身的觀點來看)放入管道中。例如以下代碼顯示了在下一個階段進行計算之前的 i 的當前值:
清單 13. 讓我如何愛上您呢?別那么冗長
- // This is Scala
- object App
- {
- def log(item : _) : Boolean =
- {
- System.out.println("Evaluating " + item)
- true
- }
- def main(args : Array[String]) =
- {
- for (val i <- 1 to 10; log(i); (i % 2) == 0)
- System.out.println("Counting " + i)
- }
- }
在運行的時候,范圍 1 到 10 中的每個項都將發(fā)送給 log,它將通過顯式計算每個項是否為 true 來 “批準” 每個項。然后,for 的第三個子句將對這些項進行篩選,過濾出那些滿足是偶數(shù)的條件的元素。因此,只將偶數(shù)傳遞給了循環(huán)主體本身。
簡單性
在 Scala 中,可以將 Java 代碼中復雜的一長串語句縮短為一個簡單的表達式。例如,以下是遍歷目錄查找所有 .scala 文件并顯示每個文件名稱的方法:
清單 14. Finding .scala
- // This is Scala
- object App
- {
- def main(args : Array[String]) =
- {
- val filesHere = (new java.io.File(".")).listFiles
- for (
- file <- filesHere;
- if file.isFile;
- if file.getName.endsWith(".scala")
- ) System.out.println("Found " + file)
- }
- }
這種 for 過濾很常見(并且在此上下文中,分號很讓人討厭),使用這種過濾是為了幫助您做出忽略分號的決定。此外,Scala 允許將上述示例中的圓括號之間的語句直接作為代碼塊對待:
清單 15. Finding .scala(版本 2)
- // This is Scala
- object App
- {
- def main(args : Array[String]) =
- {
- val filesHere = (new java.io.File(".")).listFiles
- for {
- file <- filesHere
- if file.isFile
- if file.getName.endsWith(".scala")
- } System.out.println("Found " + file)
- }
- }
作為 Java 開發(fā)人員,您可能發(fā)現(xiàn)最初的圓括號加分號的語法更直觀一些,沒有分號的曲線括號語法很難讀懂。幸運的是,這兩種句法產(chǎn)生的代碼是等效的。
一些有趣的事
在 for 表達式的子句中可以分配一個以上的項,如清單 16 中所示。
清單 16. 名稱中有什么?
- // This is Scala
- object App
- {
- def main(args : Array[String]) =
- {
- // Note the array-initialization syntax; the type (Array[String])
- // is inferred from the initialized elements
- val names = Array("Ted Neward", "Neal Ford", "Scott Davis",
- "Venkat Subramaniam", "David Geary")
- for {
- name <- names
- firstName = name.substring(0, name.indexOf(' '))
- } System.out.println("Found " + firstName)
- }
- }
這被稱為 “中途賦值(midstream assignment)”,其工作原理如下:定義了一個新值 firstName,該值用于保存每次執(zhí)行循環(huán)后的 substring 調(diào)用的值,以后可以在循環(huán)主體中使用此值。
這還引出了嵌套 迭代的概念,所有迭代都位于同一表達式中:
清單 17. Scala grep
- // This is Scala
- object App
- {
- def grep(pattern : String, dir : java.io.File) =
- {
- val filesHere = dir.listFiles
- for (
- file <- filesHere;
- if (file.getName.endsWith(".scala") || file.getName.endsWith(".java"));
- line <- scala.io.Source.fromFile(file).getLines;
- if line.trim.matches(pattern)
- ) println(line)
- }
- def main(args : Array[String]) =
- {
- val pattern = ".*object.*"
- grep pattern new java.io.File(".")
- }
- }
在此示例中,grep 內(nèi)部的 for 使用了兩個嵌套迭代,一個在指定目錄(其中每個文件都與 file 連接在一起)中找到的所有文件上進行迭代,另一個迭代在目前正被迭代的文件(與 line 本地變量連接在一起)中發(fā)現(xiàn)的所有行上進行迭代。
使用 Scala 的 for 結(jié)構(gòu)可以做更多的事,但目前為止提供的示例已足以表達我的觀點:Scala 的 for 實際上是一條管道,它在將元素傳遞給循環(huán)主體之前處理元素組成的集合,每次一個。此管道其中的一部分負責將更多的元素添加到管道中(生成器),一部分負責編輯管道中的元素(過濾器),還有一些負責處理中間的操作(比如記錄)。無論如何,Scala 會帶給您與 Java 5 中引入的 “增強的 for 循環(huán)” 不同的體驗。
匹配
今天要了解的最后一個 Scala 控制結(jié)構(gòu)是 match,它提供了許多 Scala 模式匹配功能。幸運的是,模式匹配會聲明對某個值進行計算的代碼塊。首先,將執(zhí)行代碼塊中最接近的匹配結(jié)果。因此,在 Scala 中可以包含以下代碼:
清單 18. 一個簡單的匹配
- // This is Scala
- object App
- {
- def main(args : Array[String]) =
- {
- for (arg <- args)
- arg match {
- case "Java" => println("Java is nice...")
- case "Scala" => println("Scala is cool...")
- case "Ruby" => println("Ruby is for wimps...")
- case _ => println("What are you, a VB programmer?")
- }
- }
- }
剛開始您可能將 Scala 模式匹配設(shè)想為支持 String 的 “開關(guān)’,帶有通常用作通配符的下劃線字符,而這正是典型開關(guān)中的默認情況。但是,這樣想會極大地低估該語言。模式匹配是許多(但不是大多數(shù))函數(shù)語言中可以找到的另一個特性,它提供了一些有用的功能。
對于初學者(盡管這沒什么好奇怪的),可能認為 match 表達式自身會產(chǎn)生一個值,該值可能出現(xiàn)在賦值語句的右邊,正如 if 和 try 語句所做的那樣。這一點本身也很有用,但匹配的真正威力體現(xiàn)在基于各種類型進行匹配時,而不是如上所述匹配單個類型的值,或者更多的時候,它是兩種匹配的組合。
因此,假設(shè)您有一個聲明返回 Object 的函數(shù)或方法 —— 在這里,Java 的 java.lang.reflect.Method.invoke() 方法的結(jié)果可能是一個好例子。通常,在使用 Java 語言計算結(jié)果時,首先應(yīng)該確定其類型;但在 Scala 中,可以使用模式匹配簡化該操作:
清單 19. 您是什么?
- //This is Scala
- object App
- {
- def main(args : Array[String]) =
- {
- // The Any type is exactly what it sounds like: a kind of wildcard that
- // accepts any type
- def describe(x: Any) = x match {
- case 5 => "five"
- case true => "truth"
- case "hello" => "hi!"
- case Nil => "the empty list"
- case _ => "something else"
- }
- println describe(5)
- println describe("hello")
- }
- }
因為 match 的很容易簡單明了地描述如何針對各種值和類型進行匹配的能力,模式匹配常用于解析器和解釋器中,在那里,解析流中的當前標記是與一系列可能的匹配子句匹配的。然后,將針對另一系列子句應(yīng)用下一個標記,依此類推(注意,這也是使用函數(shù)語言編寫許多語言解析器、編譯器和其他與代碼有關(guān)的工具的部分原因,這些函數(shù)語言中包括 Haskell 或 ML)。
關(guān)于模式匹配,還有許多可說的東西,但這些會將我們直接引導至 Scala 的另一個特性 case 類,我想將它留到下次再介紹。
結(jié)束語
Scala 在許多方面看起來都非常類似于 Java,但實際上只有 for 結(jié)構(gòu)存在一些相似性。核心語法元素的函數(shù)特性不僅提供了一些有用的特性(比如已經(jīng)提到的賦值功能),還提供了使用新穎有趣的方式擴展語言的能力,不必修改核心 javac 編譯器本身。這使該語言更加符合 DSL 的定義(這些 DSL 是在現(xiàn)有語言的語法中定義的),并且更加符合編程人員根據(jù)一組核心原語(a la Lisp 或 Scheme)構(gòu)建抽象的愿望。
關(guān)于 Scala,有如此多的內(nèi)容可以談?wù)?,但我們這個月的時間已經(jīng)用完了。記得試用最新的 Scala bits(在撰寫本文時是 2.7.0-final)并嘗試提供的示例,感受一下該語言的操作(請參閱 參考資料)。請記住,到下一次的時候,Scala 會將一些有趣的(函數(shù))特性放入編程中!
【相關(guān)閱讀】