您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)Scala控制結(jié)構(gòu)指的是什么,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
迄今為止,在此系列中,我們已經(jīng)討論了 Scala 對(duì)生態(tài)環(huán)境的保真度,展示了 Scala 如何將眾多的 Java 核心對(duì)象功能合并在一起。如果 Scala 只是編寫對(duì)象的另一種方式,那么它不會(huì)有任何引人注意的地方,或者說不再那么功能強(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)看起來類似一些老的、還比較不錯(cuò)的 Java 結(jié)構(gòu),但實(shí)際上 Scala 為它們?cè)黾恿艘恍┩耆煌奶匦浴1驹碌奈恼率顷P(guān)于使用 Scala 控制結(jié)構(gòu)時(shí)能夠期望獲得哪些東西的入門級(jí)讀物,而不是在制造許多錯(cuò)誤(并編寫一堆錯(cuò)誤代碼)之后,讓您冒著遭受挫折的風(fēng)險(xiǎn)去尋找差異。
修訂后的 Person.scala
Scala 能夠通過定義一些方法來定義 POJO,這些方法模仿基于 POJO 的環(huán)境所需的傳統(tǒng) “getter 和 setter”。在這篇文章發(fā)表之后,我收到了 Bill Venners 發(fā)來的電子郵件,Bill Venners 是即將發(fā)表的正式的 ,Bill 指出了實(shí)現(xiàn)上述操作的一個(gè)更簡(jiǎn)單的方法,即使用 scala.reflect.BeanProperty 標(biāo)注,如下所示:
清單 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 中的方法為指定的 var 生成了 get/set 方法對(duì)。惟一的缺陷是這些方法并不實(shí)際存在于 Scala 代碼中,因此其他 Scala 代碼無(wú)法調(diào)用它們。這通常不是什么大問題,因?yàn)?Scala 將對(duì)為自己生成的字段使用已生成的方法;如果事先不知道,那么這些對(duì)您而言可能是一個(gè)驚喜。
在查看了清單 1 中的代碼之后,最讓我感到震動(dòng)的是,Scala 并沒有只演示組合函數(shù)概念和對(duì)象概念的強(qiáng)大威力,它還演示了自 Java ***發(fā)布之后的 30 年里對(duì)象語(yǔ)言帶來的一些益處。
控制是一種幻想
您將看到的許多奇怪的、不可思議的東西都可以歸功于 Scala 的函數(shù)特性,因此,簡(jiǎn)單介紹一下函數(shù)語(yǔ)言開發(fā)和演變的背景可能非常有用。
在函數(shù)語(yǔ)言中,將越來越高級(jí)的結(jié)構(gòu)直接構(gòu)建到語(yǔ)言中是不常見的。此外,語(yǔ)言是通過一組核心原語(yǔ)結(jié)構(gòu)定義的。在與將函數(shù)作為對(duì)象傳遞的功能結(jié)合之后,可用來定義功能的高階函數(shù) 看起來 像是超出了核心語(yǔ)言的范圍,但實(shí)際上它只是一個(gè)庫(kù)。類似于任何庫(kù),此功能可以替換、擴(kuò)充或擴(kuò)展。
根據(jù)一組核心原語(yǔ)構(gòu)建語(yǔ)言的合成 特性由來已久,可以追溯到 20 世紀(jì) 60 年代和 70 年代使用 Smalltalk、Lisp 和 Scheme 的時(shí)候。諸如 Lisp 和 Scheme 之類的語(yǔ)言因?yàn)樗鼈冊(cè)诟图?jí)別的抽象上定義更高級(jí)別抽象的能力而受到人們的狂熱追捧。編程人員可以使用高級(jí)抽象,用它們構(gòu)建更高級(jí)的抽象。如今聽到討論這個(gè)過程時(shí),它通常是關(guān)于特定于域的語(yǔ)言(或 DSL)的(請(qǐng)參閱 參考資料)。實(shí)際上,它只是關(guān)于如何在抽象之上構(gòu)建抽象的過程。
在 Java 語(yǔ)言中,惟一選擇就是利用 API 調(diào)用完成此操作;在 Scala 中,可以通過擴(kuò)展語(yǔ)言本身實(shí)現(xiàn)它。試圖擴(kuò)展 Java 語(yǔ)言會(huì)帶來創(chuàng)建極端場(chǎng)景(corner case)的風(fēng)險(xiǎn),這些場(chǎng)景將威脅全局的穩(wěn)定性。而試圖擴(kuò)展 Scala 則只意味著創(chuàng)建一個(gè)新庫(kù)。
If 結(jié)構(gòu)
我們將從傳統(tǒng)的 if 結(jié)構(gòu)開始 —— 當(dāng)然,此結(jié)構(gòu)必須是最容易處理的結(jié)構(gòu)之一,不是嗎?畢竟,從理論上說,if 只檢查一個(gè)條件。如果條件為真,則執(zhí)行后面跟著的代碼。
但是,這種簡(jiǎn)單性可能帶有欺騙性。傳統(tǒng)上,Java 語(yǔ)言對(duì) if 的 else 子句的使用是隨意的,并且假定如果條件出錯(cuò),可以只跳過代碼塊。但在函數(shù)語(yǔ)句中,情況不是這樣。為了保持函數(shù)語(yǔ)句的算術(shù)特性,所有一切都必須以表達(dá)式計(jì)算的方式出現(xiàn),包括 if 子句本身(對(duì)于 Java 開發(fā)人員,這正是三元操作符 —— ?: 表達(dá)式 —— 的工作方式)。
在 Scala 中,非真代碼塊(代碼塊的 else 部分)必須以與 if 代碼塊中值種類相同的形式呈現(xiàn),并且必須產(chǎn)生同一種類的值。這意味著不論以何種方式執(zhí)行代碼,總會(huì)產(chǎn)生一個(gè)值。例如,請(qǐng)參見以下 Java 代碼:
清單 2. 哪個(gè)配置文件?(Java 版)
// This is Java String filename = "default.properties"; if (options.contains("configFile")) filename = (String)options.get("configFile");
因?yàn)?Scala 中的 if 結(jié)構(gòu)自身就是一個(gè)表達(dá)式,所以重寫上述代碼會(huì)使它們成為清單 3 中所示的更正確的代碼片段:
清單 3. 哪個(gè)配置文件?(Scala 版)
// This is Scala val filename = if (options.contains("configFile")) options.get("configFile") else "default.properties"
也就是說,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)開始,這是展示上述情況的一種方法,甚至對(duì)最堅(jiān)定的 Java 懷疑論者也是如此。從 Java 中的 final 開始介紹可能不是很合理,或許是因?yàn)?Java 的非函數(shù)特性,盡管此原因不可取。一些好奇的 Java 開發(fā)人員可能想嘗試一下。
盡管真正的贏家是 Scala,但可以通過編寫代碼將結(jié)果分配給 val,而不是 var。在設(shè)置之后,就無(wú)法對(duì) val 進(jìn)行更改,這與 Java 語(yǔ)言中 final 變量的操作方式是相同的。不可變本地變量最顯著的副作用是很容易實(shí)現(xiàn)并發(fā)性。試圖用 Java 代碼實(shí)現(xiàn)同樣的操作時(shí),會(huì)帶來許多不錯(cuò)的、易讀的好代碼,如清單 4 中所示:
清單 4. 哪個(gè)配置文件?(Java 版,三元式)
//This is Java final String filename = options.contains("configFile") ? options.get("configFile") : "default.properties";
用代碼評(píng)審解釋這一點(diǎn)可能需要點(diǎn)技巧。也許這樣做是正確的,但許多 Java 編程人員會(huì)不以為然并且詢問 “您做那個(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)它都提供。
已公開的 while 結(jié)構(gòu)
接下來,讓我們來看一下 while 及其同胞 do-while。它們做的基本上是同一件事:測(cè)試一個(gè)條件,如果該條件為真,則繼續(xù)執(zhí)行提供的代碼塊。
通常,函數(shù)語(yǔ)言會(huì)避開 while 循環(huán),因?yàn)?while 實(shí)現(xiàn)的大多數(shù)操作都可以使用遞歸來完成。函數(shù)語(yǔ)言真地非常類似于 遞歸。例如,可以考慮一下 “Scala by Example”(請(qǐng)參閱 參考資料)中展示的 quicksort 實(shí)現(xiàn),該實(shí)現(xiàn)可以與 Scala 實(shí)現(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; }
不必深入太多的細(xì)節(jié),就可以了解 while 循環(huán)的用法,它是通過數(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)行排序。
這足以說明清單 5 中的 quicksort 真的不太容易讀取,更不用說理解它?,F(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 版。也就是說,該代碼很長(zhǎng),很難看,并且難以理解(特別是并發(fā)性那一部分),明顯不具備 Java 版的一些優(yōu)點(diǎn)。
所以,我將其改進(jìn)……
清單 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 代碼更簡(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 狂熱愛好者認(rèn)為這是編寫 Scala 代碼的正確方式。
可能值得一提的是,Scala 本身并不介意您是否使用 while 代替迭代 —— 您會(huì)看到來自編譯器的 “您在干什么,在做蠢事嗎?” 的警告。Scala 也不會(huì)阻止您在可變狀態(tài)下編寫代碼。但是,使用 while 或可變狀態(tài)意味著犧牲 Scala 語(yǔ)言的另一個(gè)關(guān)鍵方面,即鼓勵(lì)編寫具有良好并行性的代碼。只要有可能并且可行,“Scala 式作風(fēng)” 會(huì)建議您優(yōu)先在命令塊上執(zhí)行遞歸。
編寫自己的語(yǔ)言結(jié)構(gòu)
我想走捷徑來討論一下 Scala 的控制結(jié)構(gòu),做一些大多數(shù) Java 開發(fā)人員根本無(wú)法相信的事 —— 創(chuàng)建自己的語(yǔ)言結(jié)構(gòu)。
那些通過死讀書學(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)行了解釋說明:
// This is Scala def While (p: => Boolean) (s: => Unit) { if (p) { s ; While(p)(s) } }
上述語(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. 如果最初沒有獲得成功……
// This is Scala val url = try { new URL(possibleURL) } catch { case ex: MalformedURLException => new URL("www.tedneward.com") }
清單 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è)常見特性,稍后將介紹它;現(xiàn)在,只把它看作一個(gè)將用于 switch/case 的概念,那么哪種 C 風(fēng)格的 struct 將用于類呢?
現(xiàn)在,再來考慮一下異常處理。眾所周知,Scala 支持異常處理是因?yàn)樗且粋€(gè)表達(dá)式,但開發(fā)人員想要的是處理異常的標(biāo)準(zhǔn)方法,并不僅僅是捕獲異常的能力。在 AspectJ 中,是通過創(chuàng)建方面(aspect)來實(shí)現(xiàn)這一點(diǎn)的,這些方面圍繞代碼部分進(jìn)行聯(lián)系,它們是通過切入點(diǎn)定義的,如果想讓數(shù)據(jù)庫(kù)的不同部分針對(duì)不同種類異常采取不同行為,那么必須小心編寫這些切入點(diǎn) —— SQLExceptions 的處理應(yīng)該不同于 IOExceptions 的處理,依此類推。
在 Scala 中,這只是微不足道的細(xì)節(jié)。請(qǐng)留神觀察!
清單 9. 一個(gè)自定義異常表達(dá)式
// 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 代碼只是來自某個(gè)庫(kù)的函數(shù)調(diào)用(在這里,是來自同一個(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)過檢查的和未經(jīng)檢查的都包括)并以特定方式進(jìn)行處理。上述想法的副作用非常多,惟一的限制也許就是想象力了。您只需記得 Scala 像許多函數(shù)語(yǔ)言一樣允許使用代碼塊(aka 函數(shù))作為參數(shù)并根據(jù)需要使用它們即可。
"for" 生成語(yǔ)言
所有這些都引導(dǎo)我們來到了 Scala 控制結(jié)構(gòu)套件的實(shí)際動(dòng)力源泉:for 結(jié)構(gòu)。該結(jié)構(gòu)看起來像是 Java 的增強(qiáng) for 循環(huán)的簡(jiǎn)單早期版,但它遠(yuǎn)比一般的 Java 編程人員開始設(shè)想的更強(qiáng)大。
讓我們來看一下 Scala 如何處理集合上的簡(jiǎn)單順序迭代,根據(jù)您的 Java 編程經(jīng)驗(yàn),我想您應(yīng)該非常清楚該怎么做:
清單 10. 對(duì)一個(gè)對(duì)象使用 for 循環(huán)和對(duì)所有對(duì)象使用 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 次,并且每次都輸出一些值。需要小心的是:表達(dá)式 “1 to 10” 并不意味著 Scala 內(nèi)置了整數(shù)感知(awareness of integer)以及從 1 到 10 的計(jì)數(shù)方式。從技術(shù)上說,這里存在一些更微妙的地方:編譯器使用 Int 類型上定義的方法 to 生成一個(gè) Range 對(duì)象(Scala 中的任何東西都是對(duì)象,還記得嗎?),該對(duì)象包含要迭代的元素。如果用 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) } }
實(shí)際上,Scala 的 for 并不了解那些成員,并且并不比其他任何對(duì)象類型做得更好。它所了解的是 scala.Iterable,scala.Iterable 定義了在集合上進(jìn)行迭代的基本行為。提供 Iterable 功能(從技術(shù)上說,它是 Scala 中的一個(gè)特征,但現(xiàn)在將它視為一個(gè)接口)的任何東西都可以用作 for 表達(dá)式的核心。List、Array,甚至是您自己的自定義類型,都可以在 for 中使用。
讓 Scala 與英語(yǔ)更接近
您可能已經(jīng)注意到,理解清單 11 中的 Scala 的 for 循環(huán)版本更容易一些。這要感謝 Range 對(duì)象暗中將兩端都包含在內(nèi),以下英語(yǔ)語(yǔ)言語(yǔ)法比 Java 語(yǔ)言更接近些。假如有一條 Range 語(yǔ)句說 “from 1 to 10, do this”,那么這意味著不再產(chǎn)生意外的 off-by-one 錯(cuò)誤。
特殊性
正如上面已經(jīng)證明的那樣,for 循環(huán)可以做許多事情,并不只是遍歷可迭代的項(xiàng)列表。事實(shí)上,可以使用一個(gè) for 循環(huán)在操作過程中過濾許多項(xiàng),并在每個(gè)階段都產(chǎn)生一個(gè)新列表:
清單 12. 看一看還有哪些優(yōu)點(diǎn)
// 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 表達(dá)式的第二個(gè)子句了嗎?它是一個(gè)過濾器,實(shí)際上,只有那些傳遞給過濾器(即計(jì)算 true)的元素 “向前傳給” 了循環(huán)主體。在這里,只輸出了 1 到 10 的偶數(shù)數(shù)字。
并不要求 for 表達(dá)式的各個(gè)階段都成為過濾器。您甚至可以將一些完全平淡無(wú)奇的東西(從循環(huán)本身的觀點(diǎn)來看)放入管道中。例如以下代碼顯示了在下一個(gè)階段進(jìn)行計(jì)算之前的 i 的當(dāng)前值:
清單 13. 讓我如何愛上您呢?別那么冗長(zhǎng)
// 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) } }
在運(yùn)行的時(shí)候,范圍 1 到 10 中的每個(gè)項(xiàng)都將發(fā)送給 log,它將通過顯式計(jì)算每個(gè)項(xiàng)是否為 true 來 “批準(zhǔn)” 每個(gè)項(xiàng)。然后,for 的第三個(gè)子句將對(duì)這些項(xiàng)進(jìn)行篩選,過濾出那些滿足是偶數(shù)的條件的元素。因此,只將偶數(shù)傳遞給了循環(huán)主體本身。
簡(jiǎn)單性
在 Scala 中,可以將 Java 代碼中復(fù)雜的一長(zhǎng)串語(yǔ)句縮短為一個(gè)簡(jiǎn)單的表達(dá)式。例如,以下是遍歷目錄查找所有 .scala 文件并顯示每個(gè)文件名稱的方法:
清單 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 過濾很常見(并且在此上下文中,分號(hào)很讓人討厭),使用這種過濾是為了幫助您做出忽略分號(hào)的決定。此外,Scala 允許將上述示例中的圓括號(hào)之間的語(yǔ)句直接作為代碼塊對(duì)待:
清單 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)最初的圓括號(hào)加分號(hào)的語(yǔ)法更直觀一些,沒有分號(hào)的曲線括號(hào)語(yǔ)法很難讀懂。幸運(yùn)的是,這兩種句法產(chǎn)生的代碼是等效的。
一些有趣的事
在 for 表達(dá)式的子句中可以分配一個(gè)以上的項(xiàng),如清單 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)”,其工作原理如下:定義了一個(gè)新值 firstName,該值用于保存每次執(zhí)行循環(huán)后的 substring 調(diào)用的值,以后可以在循環(huán)主體中使用此值。
這還引出了嵌套 迭代的概念,所有迭代都位于同一表達(dá)式中:
清單 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 使用了兩個(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é)編輯管道中的元素(過濾器),還有一些負(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)單的匹配
// 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)中的默認(rèn)情況。但是,這樣想會(huì)極大地低估該語(yǔ)言。模式匹配是許多(但不是大多數(shù))函數(shù)語(yǔ)言中可以找到的另一個(gè)特性,它提供了一些有用的功能。
對(duì)于初學(xué)者(盡管這沒什么好奇怪的),可能認(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. 您是什么?
//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") } }
因?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)于模式匹配,還有許多可說的東西,但這些會(huì)將我們直接引導(dǎo)至 Scala 的另一個(gè)特性 case 類,我想將它留到下次再介紹。
Scala 在許多方面看起來都非常類似于 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)建抽象的愿望。
以上就是Scala控制結(jié)構(gòu)指的是什么,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。