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

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

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Scala編程指南揭示Scala的本質(zhì)

Scala 是一種基于JVM,集合了面向?qū)ο缶幊毯秃瘮?shù)式編程優(yōu)點的高級程序設(shè)計語言。在《Scala編程指南 更少的字更多的事》中我們從幾個方面見識了Scala 簡潔,可伸縮,高效的語法。我們也描述了許多Scala 的特性。本文為《Programming Scala》第三章,我們會在深入Scala 對面向?qū)ο缶幊毯秃瘮?shù)式編程的支持前,完成對Scala 本質(zhì)的講解。

成都創(chuàng)新互聯(lián)公司制作網(wǎng)站網(wǎng)頁找三站合一網(wǎng)站制作公司,專注于網(wǎng)頁設(shè)計,成都網(wǎng)站建設(shè)、成都網(wǎng)站制作,網(wǎng)站設(shè)計,企業(yè)網(wǎng)站搭建,網(wǎng)站開發(fā),建網(wǎng)站業(yè)務(wù),680元做網(wǎng)站,已為成百上千服務(wù),成都創(chuàng)新互聯(lián)公司網(wǎng)站建設(shè)將一如既往的為我們的客戶提供最優(yōu)質(zhì)的網(wǎng)站建設(shè)、網(wǎng)絡(luò)營銷推廣服務(wù)!

推薦專題:Scala編程語言

Scala 本質(zhì)

在我們深入Scala 對面向?qū)ο缶幊桃约昂瘮?shù)式編程的支持之前,讓我們來先完成將來可能在程序中用到的一些Scala 本質(zhì)和特性的討論。

操作符?操作符?

Scala 一個重要的基礎(chǔ)概念就是所有的操作符實際上都是方法??紤]下面這個最基礎(chǔ)的例子。

 
 
 
  1. // code-examples/Rounding/one-plus-two-script.scala  
  2. 1 + 2 

兩個數(shù)字之間的加號是個什么呢?是一個方法。第一,Scala 允許非字符型方法名稱。你可以把你的方法命名為+,-,$ 或者其它任何你想要的名字(譯著:后面會提到例外)。第二,這個表達(dá)式等同于1 .+(2)。(我們在1 的后面加了一個空格因為1. 會被解釋為Double 類型。)當(dāng)一個方法只有一個參數(shù)的時候,Scala 允許你不寫點號和括號,所以方法調(diào)用看起來就像是操作符調(diào)用。這被稱為“中綴表示法”,也就是操作符是在實例和參數(shù)之間的。我們會很快見到很多這樣的例子。

類似的,一個沒有參數(shù)的方法也可以不用點號,直接調(diào)用。這被稱為“后綴表示法”。

Ruby 和SmallTalk 程序員現(xiàn)在應(yīng)該感覺和在家一樣親切了。因為那些語言的使用者知道,這些簡單的規(guī)則有著廣大的好處,它可以讓你用自然的,優(yōu)雅的方式來創(chuàng)建應(yīng)用程序。

那么,哪些字符可以被用在標(biāo)識符里呢?這里有一個標(biāo)識符規(guī)則的概括,它應(yīng)用于方法,類型和變量等的名稱。要獲取更精確的細(xì)節(jié)描述,參見[ScalaSpec2009]。Scala 允許所有可打印的ASCII 字符,比如字母,數(shù)字,下劃線,和美元符號$,除了括號類的字符,比如‘(’, ‘)’, ‘[’, ‘]’, ‘{’, ‘}’,和分隔類字符比如‘`’, ‘’’, ‘'’, ‘"’, ‘.’, ‘;’, 和 ‘,’。除了上面的列表,Scala 還允許其他在u0020 到u007F 之間的字符,比如數(shù)學(xué)符號和“其它” 符號。這些余下的字符被稱為操作符字符,包括了‘/’, ‘<’ 等。

1 不能使用保留字

正如大多數(shù)語言一樣,你不能是用保留字作為標(biāo)識符。我們在《第2章 - 打更少的字,做更多的事》的“保留字” 章節(jié)列出了所有的保留字?;貞浺幌?,其中有些保留字是操作符和標(biāo)點的組合。比如說,簡單的一個下劃線(‘_’) 是一個保留字!

2 普通標(biāo)識符 - 字母,數(shù)字以及 ‘$’, ‘_’ , 操作符的組合

和Java 以及很多其它語言一樣,一個普通標(biāo)志符可以以一個字母或者下劃線開頭,緊跟著更多的字母,數(shù)字,下劃線和美元符號。和Unicode 等同的字符也是被允許的。然而,和Java 一樣,Scala 保留了美元符號作為內(nèi)部使用,所以你不應(yīng)該在你自己的標(biāo)識符里使用它。在一個下劃線之后,你可以接上字母,數(shù)字,或者一個序列的操作符字符。下劃線很重要,它告訴編譯器把后面直到空格之前所有的字符都處理為標(biāo)識符。比如,val xyz__++ = 1 把值1 賦值給變量xyz__++,而表達(dá)式val xyz++= = 1卻不能通過編譯,因為這個標(biāo)識符同樣可以被解釋為xyz ++=,看起來像是要把某些東西加到xyz 后面去。類似的,如果你在下劃線后接有操作符字符,你不能把它們和字母數(shù)字混合在一起。這個約束避免了像這樣的表達(dá)式的二義性:abc_=123。這是一個標(biāo)識符abc_=123 還是給abc_ 賦值123 呢?

3 普通標(biāo)識符 - 操作符

如果一個標(biāo)識符以操作符為開頭,那么余下的所有字符都必須是操作符字符。

4 反引用字面值

一個標(biāo)識符可以是兩個反單引號內(nèi)一個任意的字符串(受制于平臺的限制)。比如val `this is a valid identifier` = "Hello World!"。回憶一下我們可以發(fā)現(xiàn),這個語法也是引用Java 或者.NET 的類庫中和Scala 保留字的名稱一樣的方法時候所用的方式,比如java.net.Proxy.`type`()。

5 模式匹配標(biāo)識符

在模式匹配表達(dá)式中,以小寫字母開頭的標(biāo)識都會被解析為變量標(biāo)識符,而以大寫字母開頭的標(biāo)識會被解析為常量標(biāo)識符。這個限定避免了一些由于非常簡潔的變量語法而帶來的二義性,例如:不用寫val 關(guān)鍵字。

語法糖蜜

一旦你知道所有的操作符都是方法,那么理解一些不熟悉的Scala 代碼就會變的相對容易些了。你不用擔(dān)心那些充滿了新奇操作符的特殊案例。在《第1章 - 從0 分到60 分:Scala 介紹》中的“初嘗并發(fā)” 章節(jié)中,我們使用了Actor 類,你會注意到我們使用了一個驚嘆號(?。﹣戆l(fā)送消息給一個Actor?,F(xiàn)在你知道!只是另外一個方法而已,就像其它你可以用來和Actor 交互的快捷操作符一樣。類似的,Scala 的XML 庫提供了 操作符來滲入到文檔結(jié)構(gòu)中去。這些只是scala.xml.NodeSeq 類的方法而已。

靈活的方法命名規(guī)則能讓你寫出就像Scala 原生擴展一樣的庫。你可以寫一個數(shù)學(xué)類庫,處理數(shù)字類型,加減乘除以及其它常見的數(shù)學(xué)操作。你也可以寫一個新的行為類似Actors 的并發(fā)消息層。各種的可能性僅受到Scala 方法命名限制的約束。

警告

別因為你可以就覺得你應(yīng)該這么作。當(dāng)用Scala 來設(shè)計你自己的庫和API 的時候,記住,晦澀的標(biāo)點和操作符會難以被程序員所記住。過量使用這些操作符會導(dǎo)致你的代碼充滿難懂的噪聲。堅持已有的約定,當(dāng)一個快捷符號沒有在你腦海中成型的時候,清晰地把它拼出來吧。

不用點號和括號的方法

為了促進(jìn)閱讀性更加的編程風(fēng)格,Scala 在方法的括號使用上可謂是靈活至極。如果一個方法不用接受參數(shù),你可以無需括號就定義它。調(diào)用者也必須不加括號地調(diào)用它。如果你加上了空括號,那么調(diào)用者可以有選擇地加或者不加括號。例如,List 的size 方法沒有括號,所以你必須寫List(1,2,3).size。如果你嘗試寫List(1,2,3).size() 就會得到一個錯誤。然而,String 類的length 方法在定義時帶有括號,所以,"hello".length() 和"hello".length 都可以通過編譯。

Scala 社區(qū)的約定是,在沒有副作用的前提下,省略調(diào)用方法時候的空括號。所以,查詢一個序列的大?。╯ize)的時候可以不用括號,但是定義一個方法來轉(zhuǎn)換序列的元素則應(yīng)該寫上括號。這個約定給你的代碼使用者發(fā)出了一個有潛在的巧妙方法的信號。

當(dāng)調(diào)用一個沒有參數(shù)的方法,或者只有一個參數(shù)的方法的時候,還可以省略點號。知道了這一點,我們的List(1,2,3).size 例子就可以寫成這樣:

 
 
 
  1. // code-examples/Rounding/no-dot-script.scala  
  2. List(1, 2, 3) size 

很整潔,但是又令人疑惑。在什么時候這樣的語法靈活性會變得有用呢?是當(dāng)我們把方法調(diào)用鏈接成自表達(dá)性的,自我解釋的語“句” 的時候:

 
 
 
  1. // code-examples/Rounding/no-dot-better-script.scala  
  2. def isEven(n: Int) = (n % 2) == 0  
  3. List(1, 2, 3, 4) filter isEven foreach println 

就像你所猜想的,運行上面的代碼會產(chǎn)生如下輸出:

 
 
 
  1. 24 

Scala 這種對于方法的括號和點號不拘泥的方式為書寫域特定語言(Domain-Specific Language)定了基石。我們會在簡短地討論一下操作符優(yōu)先級之后再來學(xué)習(xí)它。

優(yōu)先級規(guī)則

那么,如果這樣一個表達(dá)式:2.0 * 4.0 / 3.0 * 5.0 實際上是Double  上的一系列方法調(diào)用,那么這些操作符的調(diào)用優(yōu)先級規(guī)則是什么呢?這里從低到高表述了它們的優(yōu)先級[ScalaSpec2009]。

◆所有字母

◆|

◆^

◆&

◆< >

◆= !

◆:

◆+ -

◆* / %

◆所有其它特殊字符

在同一行的字符擁有同樣的優(yōu)先級。一個例外是當(dāng)= 作為賦值存在時,它擁有最低的優(yōu)先級。

因為* 和/ 有一樣的優(yōu)先級,下面兩行scala 對話的行為是一樣的。

 
 
 
  1. scala> 2.0 * 4.0 / 3.0 * 5.0res2: Double = 13.333333333333332  
  2. scala> (((2.0 * 4.0) / 3.0) * 5.0)res3: Double = 13.333333333333332 

在一個左結(jié)合的方法調(diào)用序列中,它們簡單地進(jìn)行從左到右的綁定。你說“左綁定”?在Scala 中,任何以冒號: 結(jié)尾的方法實際上是綁定在右邊的,而其它方法則是綁定在左邊。舉例來說,你可以使用:: 方法(稱為“cons”,“constructor” 構(gòu)造器的縮寫)在一個List 前插入一個元素。

 
 
 
  1. scala> val list = List('b', 'c', 'd')  
  2. list: List[Char] = List(b, c, d)  
  3. scala> 'a' :: list  
  4. res4: List[Char] = List(a, b, c, d) 

第二個表達(dá)式等效于list.::(a)。在一個右結(jié)合的方法調(diào)用序列中,它們從右向左綁定。那左綁定和有綁定混合的表達(dá)式呢?

 
 
 
  1. scala> 'a' :: list ++ List('e', 'f')  
  2. res5: List[Char] = List(a, b, c, d, e, f) 

(++ 方法鏈接了兩個list。)在這個例子里,list 被加入到List(e,f) 中,然后a 被插入到前面來創(chuàng)建最后的list。通常我們最好加上括號來消除可能的不確定因素。

提示

任何名字以: 結(jié)尾的方法都向右邊綁定,而不是左邊。

最后,注意當(dāng)你使用scala 命令的時候,無論是交互式還是使用腳本,看上去都好像可以在類型之外定義“全局”變量和方法。這其實是一個假象;解釋器實際上把所有定義都包含在一個匿名的類型中,然后才去生成JVM 或者.NET CLR 字節(jié)碼。

#p#

領(lǐng)域特定語言

領(lǐng)域特定語言,也稱為DSL,為特定的問題領(lǐng)域提供了一種方便的語意來表達(dá)自己的目標(biāo)。比如,SQL 為處理與數(shù)據(jù)庫打交道的問題,提供了剛剛好的編程語言功能,使之成為一個領(lǐng)域特定語言。

有些DSL 像SQL 一樣是自我包含的,而今使用成熟語言來實現(xiàn)DSL 使之成為母語言的一個子集變得流行起來。這允許程序員充分利用宿主語言來涵蓋DSL 不能覆蓋到的邊緣情況,而且節(jié)省了寫詞法分析器,解析器和其它語言基礎(chǔ)的時間。

Scala 的豐富,靈活的語法使得寫DSL 輕而易舉。你可以把下面的例子看作使用Specs 庫(參見“Specs” 章節(jié))來編寫行為驅(qū)動開發(fā)[BDD] 程序的風(fēng)格。

 
 
 
  1. // code-examples/Rounding/specs-script.scala  
  2. // Example fragment of a Specs script. Doesn't run standalone  
  3.  
  4. "nerd finder" should {  
  5.   "identify nerds from a List" in {  
  6.     val actors = List("Rick Moranis", "James Dean", "Woody Allen")  
  7.     val finder = new NerdFinder(actors)  
  8.     finder.findNerds mustEqual List("Rick Moranis", "Woody Allen")  
  9.   }  
  10. }  

注意這段代碼和英語語法的相似性:“this should test that in the following scenario(這應(yīng)該在以下場景中測試它)”,“this value must equal that value (這個值必須等于那個值)”,等等。這個例子使用了華麗的Specs 庫,它提供了一套高效的DSL 來用于行為驅(qū)動開發(fā)測試和工程方法學(xué)。通過最大化利用Scala 的自有語法和諸多方法,Specs 測試組即使對于非開發(fā)人員來說也是可讀的。

這只是對Scala 強大的DSL 的一個簡單嘗試。我們會在后面看到更多其它例子,以及在討論更高級議題的時候?qū)W習(xí)如何編寫你自己的DSL(參見《第11章 - Scala 的領(lǐng)域特定語言》)。

Scala if 指令

即使是最常見的語言特性在Scala 里也被增強了。讓我們來看看簡單的if 指令。和大多數(shù)語言一樣,Scala 的if 測試一個條件表達(dá)式,然后根據(jù)結(jié)果為真或假來跳轉(zhuǎn)到響應(yīng)語句塊中。一個簡單的例子:

 
 
 
  1. // code-examples/Rounding/if-script.scala  
  2. if (2 + 2 == 5) {  
  3.   println("Hello from 1984.")  
  4. } else if (2 + 2 == 3) {  
  5.   println("Hello from Remedial Math class?")  
  6. } else {  
  7.   println("Hello from a non-Orwellian future.")  

在Scala 中與眾不同的是,if 和其它幾乎所有指令實際上都是表達(dá)式。所以,我們可以把一個if 表達(dá)式的結(jié)果賦值給其它(變量),像下面這個例子所展示的:

 
 
 
  1. // code-examples/Rounding/assigned-if-script.scala  
  2. val configFile = new java.io.File(".myapprc")  
  3. val configFilePath = if (configFile.exists()) {  
  4.   configFile.getAbsolutePath()  
  5. } else {  
  6.   configFile.createNewFile()  
  7.   configFile.getAbsolutePath()  

注意, if 語句是表達(dá)式,意味著它們有值。在這個例子里,configFilePath 的值就是if 表達(dá)式的值,它處理了配置文件不存在的情況,并且返回了文件的絕對路徑。這個值現(xiàn)在可以在程序中被重用了,if 表達(dá)式的值只有在被使用到的時候才會被計算。

因為在Scala 里if 語句是一個表達(dá)式,所以就不需要C 類型子語言的三重條件表達(dá)式了。你不會在Scala 里看到x ? doThis() : doThat() 這樣的代碼。因為Scala 提供了一個即強大又更具有可讀性的機制。

如果我們在上面的例子里省略else 字句會發(fā)生什么?在scala 解釋器里輸入下面的代碼會告訴我們發(fā)生什么。

 
 
 
  1. scala> val configFile = new java.io.File("~/.myapprc")  
  2. configFile: java.io.File = ~/.myapprc  
  3. scala> val configFilePath = if (configFile.exists()) {  
  4.      |   configFile.getAbsolutePath()  
  5.      | }  
  6. configFilePath: Unit = ()  
  7. scala> 

注意現(xiàn)在configFilePath 是Unit 類型了。(之前是String。)類型推斷選擇了一個滿足if 表達(dá)式所有結(jié)果的類型。Unit 是唯一的可能,因為沒有值也是一個可能的結(jié)果。

Scala for 推導(dǎo)語句

Scala 另外一個擁有豐富特性的類似控制結(jié)構(gòu)是for 循環(huán),在Scala 社區(qū)中也被稱為for 推導(dǎo)語句或者for 表達(dá)式。語言的這個功能絕對對得起一個花哨的名字,因為它可以做一些很酷的戲法。

實際上,術(shù)語推導(dǎo)(comprehension)來自于函數(shù)式編程。它表達(dá)了這樣個一個觀點:我們正在遍歷某種集合,“推導(dǎo)”我們所發(fā)現(xiàn)的,然后從中計算出一些新的東西出來。

一個簡單的小狗例子

讓我們從一個基本的for 表達(dá)式開始:

 
 
 
  1. // code-examples/Rounding/basic-for-script.scala  
  2. val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund",  
  3.                      "Scottish Terrier", "Great Dane", "Portuguese Water Dog")  
  4. for (breed <- dogBreeds)  
  5.   println(breed) 

你可能已經(jīng)猜到了,這段代碼的意思是“對于列表dogBreeds 里面的每一個元素,創(chuàng)建一個臨時變量叫breed,并賦予這個元素的值,然后打印出來?!卑?- 操作符看作一個箭頭,引導(dǎo)集合中一個一個的元素到那個我們會在for 表達(dá)式內(nèi)部引用的局部變量中去。這個左箭頭操作符被稱為生成器,之所以這么叫是因為它從一個集合里產(chǎn)生獨立的值來給一個表達(dá)式用。

過濾器

那如果我們需要更細(xì)的粒度呢? Scala 的for 表達(dá)式通過過濾器來我們指定集合中的哪些元素是我們希望使用的。所以,要在我們的狗品種列表里找到所有的梗類犬,我們可以把上面的例子改成下面這樣:

 
 
 
  1. // code-examples/Rounding/filtered-for-script.scala  
  2. val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund",  
  3.                      "Scottish Terrier", "Great Dane", "Portuguese Water Dog")  
  4. for (breed <- dogBreeds  
  5.   if breed.contains("Terrier")  
  6. ) println(breed) 

如果需要給一個for 表達(dá)式添加多于一個的過濾器,用分號隔開它們:

 
 
 
  1. // code-examples/Rounding/double-filtered-for-script.scala  
  2. val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund",  
  3.                      "Scottish Terrier", "Great Dane", "Portuguese Water Dog")  
  4. for (breed <- dogBreeds  
  5.   if breed.contains("Terrier");  
  6.   if !breed.startsWith("Yorkshire")  
  7. ) println(breed) 

現(xiàn)在你已經(jīng)找到了所有不出生于約克郡的梗類犬,但愿也知道了過濾器在過程中是多么的有用。

產(chǎn)生器

如果說,你不想把過濾過的集合打印出來,而是希望把它放到程序的另外一部分去處理呢?yeild 關(guān)鍵字就是用for 表達(dá)式來生成新集合的關(guān)鍵。在下面的例子中,注意我們把for 表達(dá)式包裹在了一對大括號中,就像我們定義任何一個語句塊一樣。

提示

for 表達(dá)式可以用括號或者大括號來定義,但是使用大括號意味著你不必用分號來分割你的過濾器。大部分時間里,你會在有一個以上過濾器,賦值的時候傾向使用大括號。

 
 
 
  1. // code-examples/Rounding/yielding-for-script.scala  
  2. val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund",  
  3.                      "Scottish Terrier", "Great Dane", "Portuguese Water Dog")  
  4. val filteredBreeds = for {  
  5.   breed <- dogBreeds  
  6.   if breed.contains("Terrier")  
  7.   if !breed.startsWith("Yorkshire")  
  8. } yield breed 

在for 表達(dá)式的每一次循環(huán)中,被過濾的結(jié)果都會產(chǎn)生一個名為breed 的值。這些結(jié)果會隨著每運行而累積,最后的結(jié)果集合被賦給值filteredBreeds(正如我們上面用if 指令做的那樣)。由for-yield 表達(dá)式產(chǎn)生的集合類型會從被遍歷的集合類型中推斷。在這個例子里,filteredBreeds 的類型是List[String],因為它是類型為List[String] 的dogBreeds 列表的一個子集。

擴展的作用域

Scala 的for 推導(dǎo)語句最后一個有用的特性是它有能力把在定義在for 表達(dá)式第一部分里的變量用在后面的部分里。這個例子是一個最好的說明:

 
 
 
  1. // code-examples/Rounding/scoped-for-script.scala  
  2. val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund",  
  3.                      "Scottish Terrier", "Great Dane", "Portuguese Water Dog")  
  4. for {  
  5.   breed <- dogBreeds  
  6.   upcasedBreed = breed.toUpperCase()  
  7. } println(upcasedBreed) 

注意,即使沒有聲明upcaseBreed 為一個val,你也可以在你的for 表達(dá)式主體內(nèi)部使用它。這個方法對于想在遍歷集合的時候轉(zhuǎn)換元素的時候來說是很理想的。

最后,在《第13章 - 應(yīng)用程序設(shè)計》的“Options 和For 推導(dǎo)語句”章節(jié),我們會看到使用Options 和for 推導(dǎo)語句可以大大地減少不必要的“null” 和空判斷,從而減少代碼數(shù)量。

#p#

其它循環(huán)結(jié)構(gòu)

Scala 有幾種其它的循環(huán)結(jié)構(gòu)

Scala while 循環(huán)

和許多語言類似,while 循環(huán)在條件為真的時候會持續(xù)執(zhí)行一段代碼塊。例如,下面的代碼在下一個星期五,同時又是13號之前,每天打印一句抱怨的話:

 
 
 
  1. // code-examples/Rounding/while-script.scala  
  2.  // WARNING: This script runs for a LOOOONG time!  
  3.  import java.util.Calendar  
  4.  def isFridayThirteen(cal: Calendar): Boolean = {  
  5.    val dayOfWeek = cal.get(Calendar.DAY_OF_WEEK)  
  6.    val dayOfMonth = cal.get(Calendar.DAY_OF_MONTH)  
  7.    // Scala returns the result of the last expression in a method  
  8.    (dayOfWeek == Calendar.FRIDAY) && (dayOfMonth == 13)  
  9.  }  
  10.  while (!isFridayThirteen(Calendar.getInstance())) {  
  11.    println("Today isn't Friday the 13th. Lame.")  
  12.    // sleep for a day  
  13.    Thread.sleep(86400000)  
  14.  } 

你可以在下面找到一張表,它列舉了所有在while 循環(huán)中工作的條件操作符。

Scala do-while 循環(huán)

和上面的while 循環(huán)類似,一個do-while 循環(huán)當(dāng)條件表達(dá)式為真時持續(xù)執(zhí)行一些代碼。唯一的區(qū)別是do-while 循環(huán)在運行代碼塊之后才進(jìn)行條件檢查。要從1 數(shù)到10,我們可以這樣寫:

 
 
 
  1. // code-examples/Rounding/do-while-script.scala  
  2.  var count = 0 
  3.  do {  
  4.    count += 1  
  5.    println(count)  
  6.  } while (count < 10) 

這也展示了在Scala 中,遍歷一個集合還有一種更優(yōu)雅的方式,我們會在下一節(jié)看到。

生成器表達(dá)式

還記得我們在討論for 循環(huán)的時候箭頭操作符嗎(<-)?我們也可以讓它在這里工作。讓我們來整理一下上面的do-while 循環(huán):

 
 
 
  1. // code-examples/Rounding/generator-script.scala  
  2.  for (i <- 1 to 10)  
  3.  println(i) 

這就是所有需要的了。是Scala 的RichInt(富整型)使得這個簡潔的單行代碼成為可能。編譯器執(zhí)行了一個隱式轉(zhuǎn)換,把1,一個Int (整型),轉(zhuǎn)換成了RichInt 類型。(我們會在《第7章 - Scala 對象系統(tǒng)》的“Scala 類型結(jié)構(gòu)” 章節(jié)以及《第8章 - Scala 函數(shù)式編程》的“隱式轉(zhuǎn)換” 章節(jié)中討論這些轉(zhuǎn)換。)RichInt 定義了以訛to 方法,它接受另外一個整數(shù),然后返回一個Range.Inclusive 的實例。也就是說,Inclusive 是Rang 伴生對象(Companion Object,我們在《第1章 - 從0 分到60 分:Scala 介紹》中間要介紹過,參考《第6章 - Scala 高級面向?qū)ο缶幊獭帆@取更多信息。)的一個嵌套類。類Range 的這個嵌套類繼承了一系列方法來和序列以及可迭代的數(shù)據(jù)結(jié)構(gòu)交互,包括那些在for 循環(huán)中必然會使用到的。

順便說一句,如果你想從1 數(shù)到10 但是不包括10, 你可以使用until 來代替to,比如for (i <- 0 until 10)。

這樣就一幅清晰的圖畫展示了Scala 的內(nèi)部類庫是如何結(jié)合起來形成簡單易用的語言結(jié)構(gòu)的。

注意

當(dāng)和大多數(shù)語言的循環(huán)一起工作時,你可以使用break 來跳出循環(huán),或者continue 來繼續(xù)下一個迭代。Scala 沒有這兩個指令,但是當(dāng)編寫地道的Scala 代碼時,它們是不必要的。你應(yīng)該使用條件表達(dá)式來測試一個循環(huán)是否應(yīng)該繼續(xù),或者利用遞歸。更好的方法是,在這之前就用過濾器來出去循環(huán)中復(fù)雜的條件狀態(tài)。然而,因為大眾需求,2.8 版本的Scala 加入了對break 的支持,不過是以庫的一個方法實現(xiàn),而不是內(nèi)建的break 關(guān)鍵字。

條件操作符

Scala 從Java 和它的前輩身上借用了絕大多數(shù)的條件操作符。你可以在下面的if 指令,while 循環(huán),以及其它任何可以運用條件判斷的地方發(fā)現(xiàn)它們。

表格 3.1. 條件操作符

操作符操作描述
&&在操作符左右的值都為真的時候結(jié)果為真。右邊的值只有在左邊的值為真的時候才會被評估(短路表達(dá)式)。
||或             操作符兩邊只要有一個為真結(jié)果就為真。右邊的值只有在左邊的值為假的時候才被評估(短路表達(dá)式)。
>       大于左側(cè)的值大于右側(cè)的值時結(jié)果為真。
>=大于等于左側(cè)的值大于等于右側(cè)的值時結(jié)果為真。
<小于左側(cè)的值小于右側(cè)的值時結(jié)果為真。
<=小于等于左側(cè)的值小于右側(cè)的值時結(jié)果為真。
==等于左側(cè)的值等于右側(cè)的值時結(jié)果為真。
!=不等于左側(cè)的值不等于右側(cè)的值時結(jié)果為真。

注意&& 和|| 是“短路” 操作符。它們會在結(jié)果必然已知的情況下停止對表達(dá)式的評估。

我們會在《第6章 - Scala 高級面向?qū)ο缶幊獭返摹皩ο蟮南嗟取?章節(jié)中更深入討論對象相等性。例如,我們會看到== 在Scala 和Java 中有著不同的含義。除此以外,這些操作符大家應(yīng)該都很熟悉,所以讓我們繼續(xù)前進(jìn)到一些新的,激動人心的特性上去。

#p#

模式匹配

模式匹配是從函數(shù)式語言中引入的強大而簡潔的多條件選擇跳轉(zhuǎn)方式。你也可以把模式匹配想象成你最喜歡的C 類語言的case 指令,當(dāng)然是打了激素的。在典型的case 指令中,通常只允許對序數(shù)類型進(jìn)行匹配,產(chǎn)生一些這樣的表達(dá)式:“在i 為5 的case 里,打印一個消息;在i 為6 的case里,離開程序。”而有了Scala 的模式匹配,你的case 可以包含類型,通配符,序列,甚至是對象變量的深度檢查。

一個簡單的匹配

讓我們從模擬拋硬幣匹配一個布爾值開始:

 
 
 
  1. // code-examples/Rounding/match-boolean-script.scala  
  2. val bools = List(true, false)  
  3. for (bool <- bools) {  
  4.   bool match {  
  5.     case true => println("heads")  
  6.     case false => println("tails")  
  7.     case _ => println("something other than heads or tails (yikes!)")  
  8.   }  

看起來很像C 風(fēng)格的case 語句,對吧?唯一的區(qū)別是最后一個case 使用了下劃線'_' 通配符。它匹配了所有上面的case 中沒有定義的情況,所以它和Java、C# 中的switch 指令的default 關(guān)鍵字作用相同。

模式匹配是貪婪的;只有第一個匹配的情況會贏。所以,如果你在所有case 前方一個case _ 語句,那么編譯器會在下一個條件拋出一個“無法執(zhí)行到的代碼”的錯誤,因為沒人能跨過那個default 條件。

提示

使用case _ 來作為默認(rèn)的,“滿足所有”的匹配。

那如果我們希望獲得匹配的變量呢?

匹配中的變量

 
 
 
  1. // code-examples/Rounding/match-variable-script.scala  
  2. import scala.util.Random  
  3. val randomInt = new Random().nextInt(10)  
  4. randomInt match {  
  5.   case 7 => println("lucky seven!")  
  6.   case otherNumber => println("boo, got boring ol' " + otherNumber)  

在這個例子里,我們把通配符匹配的值賦給了一個變量叫otherNumber,然后在下面的表達(dá)式中打印出來。如果我們生成了一個7,我們會對它稱頌道德。反之,我們則詛咒它讓我們經(jīng)歷了一個不幸運的數(shù)字。

類型匹配

這些例子甚至還沒有開始接觸到Scala 的模式匹配特性的最表面。讓我們來嘗試一下類型匹配:

 
 
 
  1. // code-examples/Rounding/match-type-script.scala  
  2. val sundries = List(23, "Hello", 8.5, 'q')  
  3. for (sundry <- sundries) {  
  4.   sundry match {  
  5.     case i: Int => println("got an Integer: " + i)  
  6.     case s: String => println("got a String: " + s)  
  7.     case f: Double => println("got a Double: " + f)  
  8.     case other => println("got something else: " + other)  
  9.   }  

這次,我們從一個元素為Any 類型的List 中拉出所有元素,包括了String,Double,Int,和Char。對于前三種類型,我們讓用戶知道我們拿到了那種類型以及它們的值。當(dāng)我們拿到其它的類型(Char),我們簡單地讓用戶知道值。我們可以添加更多的類型到那個列表,它們會被最后默認(rèn)的通配符case 捕捉。

序列匹配

鑒于用Scala 工作通常意味著和序列打交道,要是能和列表、數(shù)組的長度和內(nèi)容來匹配豈不美哉?下面的例子就做到了,它測試了兩個列表來檢查它們是否包含4個元素,并且第二個元素是3。

 
 
 
  1. // code-examples/Rounding/match-seq-script.scala  
  2. val willWork = List(1, 3, 23, 90)  
  3. val willNotWork = List(4, 18, 52)  
  4. val empty = List()  
  5. for (l <- List(willWork, willNotWork, empty)) {  
  6.   l match {  
  7.     case List(_, 3, _, _) => println("Four elements, with the 2nd being '3'.")  
  8.     case List(_*) => println("Any other list with 0 or more elements.")  
  9.   }  

在第二個case 里我們使用了一個特殊的通配符來匹配一個任意大小的List,甚至0個元素,任何元素的值都行。你可以在任何序列匹配的最后使用這個模式來解除長度制約。

回憶一下我們提過的List 的“cons” 方法,::。表達(dá)式a :: list 在一個列表前加入一個元素。你也可以使用這個操作符來從一個列表中解出頭和尾。

 
 
 
  1. // code-examples/Rounding/match-list-script.scala  
  2. val willWork = List(1, 3, 23, 90)  
  3. val willNotWork = List(4, 18, 52)  
  4. val empty = List()  
  5. def processList(l: List[Any]): Unit = l match {  
  6.   case head :: tail => 
  7.     format("%s ", head)  
  8.     processList(tail)  
  9.   case Nil => println("")  
  10. }  
  11. for (l <- List(willWork, willNotWork, empty)) {  
  12.   print("List: ")  
  13.   processList(l)  

processList 方法對List 參數(shù)l 進(jìn)行匹配。像下面這樣開始一個方法定義可能看起來比較奇怪。

 
 
 
  1. def processList(l: List[Any]): Unit = l match {  
  2.   ...  

用省略號來隱藏細(xì)節(jié)以后應(yīng)該會更加清楚一些。processList 方法實際上是一個跨越了好幾行的單指令。

它先匹配head :: tail,這時head 會被賦予這個列表的第一個元素,tail 會被賦予列表剩余的部分。也就是說,我們使用:: 來從列表中解出頭和尾。當(dāng)這個case 匹配的時候,它打印出頭,然后遞歸調(diào)用processList 來處理列表尾。

第二個case 匹配空列表,Nil。它打印出一行的最后一個字符,然后終止遞歸。

元組匹配(以及守衛(wèi))

另外,如果我們只是想測試我們是否有一個有2 個元素的元組,我們可以進(jìn)行元組匹配:

 
 
 
  1. // code-examples/Rounding/match-tuple-script.scala  
  2. val tupA = ("Good", "Morning!")  
  3. val tupB = ("Guten", "Tag!")  
  4. for (tup <- List(tupA, tupB)) {  
  5.   tup match {  
  6.     case (thingOne, thingTwo) if thingOne == "Good" => 
  7.         println("A two-tuple starting with 'Good'.")  
  8.     case (thingOne, thingTwo) => 
  9.         println("This has two things: " + thingOne + " and " + thingTwo)  
  10.   }  

例子里的第二個case,我們已經(jīng)解出了元組里的值并且附給了局部變量,然后在結(jié)果表達(dá)式中使用了這些變量。

在第一個case 里,我們加入了一個新的概念:守衛(wèi)(Guard)。這個元組后面的if 條件是一個守衛(wèi)。這個守衛(wèi)會在匹配的時候進(jìn)行評估,但是只會解出本case 的變量。守衛(wèi)在構(gòu)造cases 的時候提供了額外的尺度。在這個例子里,兩個模式的唯一區(qū)別就是這個守衛(wèi)表達(dá)式,但是這樣足夠編譯器來區(qū)分它們了。

提示

回憶一下,模式匹配的cases 會被按順序依次被評估。例如,如果你的第一個case 比第二個case 更廣,那么第二個case 就不會被執(zhí)行到。(不可執(zhí)行到的代碼會導(dǎo)致一個編譯錯誤。)你可以在模式匹配的最后包含一個“default” 默認(rèn)case,可以使用下劃線通配符,或者有含義的變量名。當(dāng)使用變量時,它不應(yīng)該顯式聲明為任何類型,除非是Any,這樣它才能匹配所有情況。另外一方面,嘗試通過設(shè)計讓你的代碼規(guī)避這樣全盤通吃的條件,保證它只接受指定的意料之中的條目。

Case 類匹配

讓我們來嘗試一次深度匹配,在我們的模式匹配中檢查對象的內(nèi)容。

 
 
 
  1. // code-examples/Rounding/match-deep-script.scala  
  2. case class Person(name: String, age: Int)  
  3. val alice = new Person("Alice", 25)  
  4. val bob = new Person("Bob", 32)  
  5. val charlie = new Person("Charlie", 32)  
  6. for (person <- List(alice, bob, charlie)) {  
  7.   person match {  
  8.     case Person("Alice", 25) => println("Hi Alice!")  
  9.     case Person("Bob", 32) => println("Hi Bob!")  
  10.     case Person(name, age) => 
  11.       println("Who are you, " + age + " year-old person named " + name + "?")  
  12.   }  

從上面例子的輸出中我們可以看出,可憐的Charlie 被無視了:

 
 
 
  1. Hi Alice!  
  2. Hi Bob!  
  3. Who are you, 32 year-old person named Charlie? 

我們收線定義了一個case 類,一個特殊類型的類,我們會在《第6章 - Scala 高級面向?qū)ο缶幊獭返摹癈ase 類”章節(jié)中學(xué)到更多細(xì)節(jié)。現(xiàn)在,我們只需要知道,一個case 類允許精煉的簡單對象的構(gòu)造,以及一些預(yù)定義的方法。然后,我們的模式匹配通過檢查傳入的Person case 類的值來查找Alice 和Bob。Charlie 則直到最后那個饑不擇食的case 才被捕獲;盡管他和Bob 有一樣的年齡,但是我們同時也檢查了名字屬性。

我們后面會看到,這種類型的模式匹配和Actor 配合工作時會非常有用。Case 類經(jīng)常會被作為消息發(fā)送到Actor,對一個對象的內(nèi)容進(jìn)行深度模式匹配是“分析”這些消息的方便方式。

正則表達(dá)式匹配

正則表達(dá)式用來從有著非正式結(jié)構(gòu)的字符串中提取數(shù)據(jù)是很方便的,但是對“結(jié)構(gòu)性數(shù)據(jù)”(就是類似XML,或者JSON 那樣的格式)則不是。正則表達(dá)式是幾乎所有現(xiàn)代編程語言的共有特性之一,通常被簡稱為regexes(regex 的復(fù)數(shù),Regular Expression 的簡稱)。它們提供了一套簡明的語法來說明復(fù)雜的匹配,其中一種通常被翻譯成后臺狀態(tài)機來獲得優(yōu)化的性能。

如果已經(jīng)在其它編程語言中使用正則表達(dá)式,那么Scala 的應(yīng)該不會讓你感覺到驚訝。讓我們來看一個例子。

 
 
 
  1. // code-examples/Rounding/match-regex-script.scala  
  2. val BookExtractorRE = """Book: title=([^,]+),s+authors=(.+)""".r  
  3. val MagazineExtractorRE = """Magazine: title=([^,]+),s+issue=(.+)""".r  
  4. val catalog = List(  
  5.   "Book: title=Programming Scala, authors=Dean Wampler, Alex Payne",  
  6.   "Magazine: title=The New Yorker, issue=January 2009",  
  7.   "Book: title=War and Peace, authors=Leo Tolstoy",  
  8.   "Magazine: title=The Atlantic, issue=February 2009",  
  9.   "BadData: text=Who put this here??"  
  10. )  
  11. for (item <- catalog) {  
  12.   item match {  
  13.     case BookExtractorRE(title, authors) => 
  14.       println("Book "" + title + "", written by " + authors)  
  15.     case MagazineExtractorRE(title, issue) => 
  16.       println("Magazine "" + title + "", issue " + issue)  
  17.     case entry => println("Unrecognized entry: " + entry)  
  18.   }  

我們從兩個正則表達(dá)式開始,其中一個記錄書的信息,另外一個記錄雜志。在一個字符串上調(diào)用.r 會把它變成一個正則表達(dá)式;我們是用了原始(三重引號)字符串來避免諸多雙重轉(zhuǎn)義的反斜杠。如果你覺得字符串的.r 轉(zhuǎn)換方法不是很清晰,你也可以通過創(chuàng)建Regex 類的實例來定義正則表達(dá)式,比如new Regex("""W""")。

注意每一個正則表達(dá)式都定義了兩個捕捉組,由括號表示。每一個組捕獲記錄上的一個單獨字段,自如書的標(biāo)題或者作者。Scala 的正則表達(dá)式會把這些捕捉組翻譯成抽取器。每個匹配都會把捕獲結(jié)果設(shè)置到到對應(yīng)的字段去;要是沒有捕捉到就設(shè)為null。

這在實際中有什么意義呢?如果提供給正則表達(dá)式的文本匹配了,case BookExtractorRE(title,authors) 會把第一個捕捉組賦給title,第二個賦給authors。我們可以在case 語句的右邊使用這些值,正如我們在上面的例子里看到的。抽取器中的變量名title 和author 是隨意的;從捕捉組來的匹配結(jié)果會簡單地從左往右被賦值,你可以叫它們?nèi)魏蚊帧?/p>

這就是Scala 正則表達(dá)式的簡要介紹。scala.util.matching.Regex 類提供了幾個方便的方法來查找和替代字符串中的匹配,不管是所有的匹配還是第一個。好好利用它們。

我們不會在這里涵蓋書寫正則表達(dá)式的細(xì)節(jié)。Scala 的Regex 類使用了對應(yīng)平臺的正則表達(dá)式API(就是Java,或者.NET 的)。參考這些API 的文檔來獲取詳細(xì)信息,不同語言之間可能會存在微妙的差別。

#p#

在Case 字句中綁定嵌套變量

有時候你希望能夠綁定一個變量到匹配中的一個對象,同時又能在嵌套的對象中指定匹配的標(biāo)準(zhǔn)。我們修改一下前面一個例子,來匹配map 的鍵值對。我們把同樣的Person 對象作為值,員工ID 作為鍵。我們會給Person 加一個屬性- 角色,用來指定對應(yīng)的實例是類型層次結(jié)構(gòu)中的哪一種。

 
 
 
  1. // code-examples/Rounding/match-deep-pair-script.scala  
  2. class Role  
  3. case object Manager extends Role  
  4. case object Developer extends Role  
  5. case class Person(name: String, age: Int, role: Role)  
  6.  
  7. val alice = new Person("Alice", 25, Developer)  
  8. val bob = new Person("Bob", 32, Manager)  
  9. val charlie = new Person("Charlie", 32, Developer)  
  10.  
  11. for (item <- Map(1 -> alice, 2 -> bob, 3 ->
    網(wǎng)站欄目:Scala編程指南揭示Scala的本質(zhì)
    網(wǎng)站URL:http://www.5511xx.com/article/cdopiod.html