亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Scala是什么

發布時間:2021-11-24 09:14:34 來源:億速云 閱讀:128 作者:小新 欄目:編程語言

小編給大家分享一下Scala是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

Scala 是一種基于JVM,集合了面向對象編程和函數式編程優點的高級程序設計語言。在《Scala編程指南 更少的字更多的事》中我們從幾個方面見識了Scala 簡潔,可伸縮,高效的語法。我們也描述了許多Scala 的特性。本文為《Programming Scala》第三章,我們會在深入Scala 對面向對象編程和函數式編程的支持前,完成對Scala 本質的講解。

Scala 本質

在我們深入Scala 對面向對象編程以及函數式編程的支持之前,讓我們來先完成將來可能在程序中用到的一些Scala 本質和特性的討論。

操作符?操作符?

Scala 一個重要的基礎概念就是所有的操作符實際上都是方法。考慮下面這個最基礎的例子。

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

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

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

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

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

1 不能使用保留字

正如大多數語言一樣,你不能是用保留字作為標識符。我們在《第2章 - 打更少的字,做更多的事》的“保留字” 章節列出了所有的保留字。回憶一下,其中有些保留字是操作符和標點的組合。比如說,簡單的一個下劃線(&lsquo;_&rsquo;) 是一個保留字!

2 普通標識符 - 字母,數字以及 &lsquo;$&rsquo;, &lsquo;_&rsquo; , 操作符的組合

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

3 普通標識符 - 操作符

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

4 反引用字面值

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

5 模式匹配標識符

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

語法糖蜜

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

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

警告

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

不用點號和括號的方法

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

Scala 社區的約定是,在沒有副作用的前提下,省略調用方法時候的空括號。所以,查詢一個序列的大小(size)的時候可以不用括號,但是定義一個方法來轉換序列的元素則應該寫上括號。這個約定給你的代碼使用者發出了一個有潛在的巧妙方法的信號。

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

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

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

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

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

24

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

優先級規則

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

◆所有字母

◆|

◆^

◆&

◆< >

◆= !

◆:

◆+ -

◆* / %

◆所有其它特殊字符

在同一行的字符擁有同樣的優先級。一個例外是當= 作為賦值存在時,它擁有最低的優先級。

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

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

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

scala> val list = List('b', 'c', 'd')  list: List[Char] = List(b, c, d)  scala> 'a' :: list  res4: List[Char] = List(a, b, c, d)

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

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

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

提示

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

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

領域特定語言

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

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

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

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

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

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

Scala if 指令

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

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

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

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

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

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

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

scala> val configFile = new java.io.File("~/.myapprc")  configFile: java.io.File = ~/.myapprc  scala> val configFilePath = if (configFile.exists()) {       |   configFile.getAbsolutePath()       | }  configFilePath: Unit = ()  scala>

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

Scala for 推導語句

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

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

一個簡單的小狗例子

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

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

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

過濾器

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

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

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

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

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

產生器

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

提示

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

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

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

擴展的作用域

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

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

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

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

其它循環結構

Scala 有幾種其它的循環結構

Scala while 循環

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

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

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

Scala do-while 循環

和上面的while 循環類似,一個do-while 循環當條件表達式為真時持續執行一些代碼。唯一的區別是do-while 循環在運行代碼塊之后才進行條件檢查。要從1 數到10,我們可以這樣寫:

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

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

生成器表達式

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

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

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

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

這樣就一幅清晰的圖畫展示了Scala 的內部類庫是如何結合起來形成簡單易用的語言結構的。

注意

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

條件操作符

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

表格 3.1. 條件操作符

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

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

我們會在《第6章 - Scala 高級面向對象編程》的“對象的相等” 章節中更深入討論對象相等性。例如,我們會看到== 在Scala 和Java 中有著不同的含義。除此以外,這些操作符大家應該都很熟悉,所以讓我們繼續前進到一些新的,激動人心的特性上去。

模式匹配

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

一個簡單的匹配

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

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

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

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

提示

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

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

匹配中的變量

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

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

類型匹配

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

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

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

序列匹配

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

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

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

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

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

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

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

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

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

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

元組匹配(以及守衛)

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

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

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

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

提示

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

Case 類匹配

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

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

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

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

我們收線定義了一個case 類,一個特殊類型的類,我們會在《第6章 - Scala 高級面向對象編程》的“Case 類”章節中學到更多細節。現在,我們只需要知道,一個case 類允許精煉的簡單對象的構造,以及一些預定義的方法。然后,我們的模式匹配通過檢查傳入的Person case 類的值來查找Alice 和Bob。Charlie 則直到最后那個饑不擇食的case 才被捕獲;盡管他和Bob 有一樣的年齡,但是我們同時也檢查了名字屬性。

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

正則表達式匹配

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

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

// code-examples/Rounding/match-regex-script.scala  val BookExtractorRE = """Book: title=([^,]+),s+authors=(.+)""".r  val MagazineExtractorRE = """Magazine: title=([^,]+),s+issue=(.+)""".r  val catalog = List(    "Book: title=Programming Scala, authors=Dean Wampler, Alex Payne",    "Magazine: title=The New Yorker, issue=January 2009",    "Book: title=War and Peace, authors=Leo Tolstoy",    "Magazine: title=The Atlantic, issue=February 2009",    "BadData: text=Who put this here??"  )  for (item <- catalog) {    item match {      case BookExtractorRE(title, authors) =>       println("Book "" + title + "", written by " + authors)      case MagazineExtractorRE(title, issue) =>       println("Magazine "" + title + "", issue " + issue)      case entry => println("Unrecognized entry: " + entry)    }  }

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

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

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

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

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

在Case 字句中綁定嵌套變量

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

// code-examples/Rounding/match-deep-pair-script.scala  class Role  case object Manager extends Role  case object Developer extends Role  case class Person(name: String, age: Int, role: Role)   val alice = new Person("Alice", 25, Developer)  val bob = new Person("Bob", 32, Manager)  val charlie = new Person("Charlie", 32, Developer)   for (item <- Map(1 -> alice, 2 -> bob, 3 -> charlie)) {    item match {      case (id, p @ Person(_, _, Manager)) => format("%s is overpaid.n", p)      case (id, p @ Person(_, _, _)) => format("%s is underpaid.n", p)    }  }

這個case 對象和我們之前看到的單體對象一樣,就是多了一些特殊的case 類所屬的行為。我們最關心的是嵌套在case 子句的p @ Person(...) 。我們在閉合元組里匹配特定類型的Person 對象。我們同時希望把Person 賦給一個變量,這樣我們就能夠打印它。

Person(Alice,25,Developer) is underpaid.  Person(Bob,32,Manager) is overpaid.  Person(Charlie,32,Developer) is underpaid.

如果我們在Person 本身使用匹配標準,我們可以直接寫 p: Person。例如,前面的match 字句可以寫成這樣。

item match {    case (id, p: Person) => p.role match {      case Manager => format("%s is overpaid.n", p)      case _ => format("%s is underpaid.n", p)    }  }

主意p @ Person(...) 語法給了我們一個把嵌套的match 語句平坦化成一個語句的方法。這類似于我們在正則表達式中使用“捕捉組”來提取我們需要的子字符串時來替代把一個字符串分隔成好幾個的方法。你可以使用任何一種你偏好的方法。

使用try,catch 和finally 語句

通過使用函數式構造和強類型特性,Scala 鼓勵減少對異常和異常處理依賴的編程風格。但是當Scala 和Java 交互時,異常還是很普遍的

注意

Scala 不支持Java 那樣的異常檢查(Checked Exception)。即使在Java 中異常是檢查的,在Scala 中也會被轉換為未檢查異常。在方法的聲明中也沒有throws 子句。不過,有一個@throws 注解可以用于和Java 交互。參見《第13章 - 應用程序設計》的“注解”章節。

感謝Scala 實際上把異常處理作為了另外一種模式匹配,允許我們在遇到多樣化的異常時能做出更聰明的決定。讓我們實際地來看一個例子:

// code-examples/Rounding/try-catch-script.scala  import java.util.Calendar  val then = null val now = Calendar.getInstance()  try {    now.compareTo(then)  } catch {    case e: NullPointerException => println("One was null!"); System.exit(-1)    case unknown => println("Unknown exception " + unknown); System.exit(-1)  } finally {    println("It all worked out.")    System.exit(0)  }

在上面的例子里,我們顯示地撲捉了NullPointerException 異常,它在嘗試把一個Calendar 實例和null 被拋出。我們同時也將unknown 定義為捕捉所有異常的字句,以防萬一。如果我們沒有硬編碼使得程序失敗,finally 塊會被執行到,用戶會被告知,一切正常。

注意

你可以使用一個下劃線(Scala 的標準通配符)作為占位符來捕捉任意類型的異常(不騙你,它可以匹配模式匹配表達式的任何case)。然而,如此你就不能再訪問下面表達式中的異常了。如果需要,你還可以命名那個異常。例如,如果你需要打印出異常信息,就像我們在前一個例子中的全獲性case 中做的一樣,e 或者ex 都是一個不錯的名字。

有了模式匹配,Scala 異常操作的處理對于熟悉Java,Ruby,Python 和其它主流語言的人來說應該很容易上手。而且一樣的,你可以通過寫throw new MyBadException(...) 來拋出異常。這就是有關異常的一切了。

模式匹配結束語

模式匹配在使用恰當時是一個強大的,優雅的從對象中抽取信息的方式。回顧《第1章 - 從0 分到60 分:Scala 介紹》中,我們強調了模式匹配和多態之間的協作。大多數時候,你希望能夠在清楚類結構的時候避免“switch”語句,因為它們必須在每次結構改變的同時被改變。

在我們的畫畫執行者(Actor)例子中,我們使用了模式匹配來分離不同的消息“種類”,但是我們使用了多態來畫出我們傳給它的圖形。我們可以修改Shape 繼承結構,Actor 部分的代碼卻不需要修改。

在你遇到需要從對象內部提取數據的設計問題時,模式匹配也有用,但是僅限一些特殊的情況。JavaBean 規格的一個沒有預料到的結果是它鼓勵人們通過getters 和setters 來暴露對象內部的字段。這從來都不應該是一個默認的決策。對“狀態”信息的存取應該被封裝,并且只在對于該類型有邏輯意義的時候被暴露,和對其抽象的觀察一致。

相反地,在你需要通過可控方式獲取信息的時候,考慮使用模式匹配。正如我們即將在《第6章 - Scala 高級面向對象編程》中的“取消應用(Unapply)”章節看到的,我們所展示的模式匹配例子使用了預定義的unapply 方法來從實例中獲取信息。這些方法讓你在不知道實現細節的同時獲取了這些信息。實際上,unapply 方法返回的信息可能是實例中實際信息的變種。

最后,當設計模式匹配指令時,對于默認case 的依賴要小心。在什么情況下“以上都不匹配”才是正確的答案?它可能象征著設計需要被完善,以便于你更精確地知道所有可能發生的匹配。我們會在《第7章 - Scala 對象系統》的“完成類(sealed class)結構”討論完成類的結構時學習到其中一種技術。

枚舉

還記得我們上一個涉及到很多種狗的例子嗎?在思考這些程序的類型時,我們可能會需要一個頂層的Breed 類型來記錄一定數量的breeds。 這樣一個類型被稱為枚舉類型,它所包含的值被稱為枚舉值。

雖然枚舉是許多程序語言的內置支持,Scala 走了一條不同的路,把它作為標準庫的一個類來實現。這意味著Scala 中沒有Java 和C# 那樣特殊的枚舉語法。相反,你只是定義個對象,讓它從Enumeration 類繼承。因此,在字節碼的層次,Scala 枚舉和Java,C# 中構造的枚舉沒有任何聯系。

這里有一個例子:

// code-examples/Rounding/enumeration-script.scala  object Breed extends Enumeration {    val doberman = Value("Doberman Pinscher")    val yorkie = Value("Yorkshire Terrier")    val scottie = Value("Scottish Terrier")    val dane = Value("Great Dane")    val portie = Value("Portuguese Water Dog")  }  // print a list of breeds and their IDs  println("IDtBreed")  for (breed <- Breed) println(breed.id + "t" + breed)  // print a list of Terrier breeds  println("nJust Terriers:")  Breed.filter(_.toString.endsWith("Terrier")).foreach(println)

運行時,你會得到如下輸出:

ID      Breed  0       Doberman Pinscher  1       Yorkshire Terrier  2       Scottish Terrier  3       Great Dane  4       Portuguese Water Dog  Just Terriers:  Yorkshire Terrier  Scottish Terrier

你可以看到我們的Breed 枚舉類型包含了幾種Value 類型的值,像下面的例子所展示的。

val doberman = Value("Doberman Pinscher")

每一個聲明實際上都調用了一個名為Value 的方法,它接受一個字符串參數。我們使用這個方法來給每一個枚舉值賦一個長的品種名稱,也就是上面輸出中的Value.toString 方法所返回的值。

注意類型和方法名字都為Value 并沒有名稱空間的沖突。我們還有其他Value 方法的重載。其中一個沒有參數,另外一個接受整型ID 值,還有一個同時接收整型和字符串參數。這些Value 方法返回一個Value 對象,它們會把這些值加到枚舉值的集合中去。

實際上,Scala 的枚舉類支持和集合協作需要的一般方法,所以我們可以簡單地在循環中遍歷這些種類,或者通過名字過濾它們。上面的輸出也展示了枚舉中的每一個Value 都被自動賦予一個數字標識,除非你調用其中一個Value 方法顯式指定ID 值。

你常常希望給你的枚舉值可讀的名字,就像我們這里做的一樣。然而,有些時候你可能不需要它們。這里有另一個從Scala 文檔中中改編過來的枚舉例子。

// code-examples/Rounding/days-enumeration-script.scala  object WeekDay extends Enumeration {    type WeekDay = Value   val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value }  import WeekDay._  def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)  WeekDay filter isWorkingDay foreach println

運行這段腳本會產生如下輸出:

Main$$anon$1$WeekDay(0)  Main$$anon$1$WeekDay(1)  Main$$anon$1$WeekDay(2)  Main$$anon$1$WeekDay(3)  Main$$anon$1$WeekDay(4)

當名字沒有用接受字符串的Value 方法構造的時候,Value。toString 打印出來的名字是由編譯器生成的,捆綁了自動生成的ID 值。

注意我們導入了WeekDay._。這使得每一個枚舉值,比如Mon,Tue 等都暴露在了可見域里。否則,你必須寫完整的WeekDay.Mon,WeekDay.Tue 等。

同時,import 使得類型別名,類型 WeekDay = Value 也暴露在了可見域里,我們在isWorkingDay 方法中接受一個該類型的參數。如果你不定義這樣的別名,你就要像這樣聲明這個方法: def isWorkingDay(d:WeekDay.Value)。

因為Scala 的枚舉值是通常的對象,你可以使用任何val 對象來指示不同的“枚舉值”。然而,擴展枚舉有幾個優勢。比如它自動把所有值作為集合以供遍歷等,正如我們的例子所示。它同時也自動對每一個值都賦予一個唯一的整型ID。

以上是“Scala是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

铜梁县| 新兴县| 大理市| 芮城县| 蚌埠市| 磐石市| 博罗县| 麻江县| 庆阳市| 馆陶县| 垣曲县| 岢岚县| 微博| 加查县| 贵阳市| 呼伦贝尔市| 巨鹿县| 卓资县| 镇康县| 金乡县| 内江市| 调兵山市| 敦煌市| 娄底市| 磴口县| 廉江市| 孝义市| 郑州市| 蓬溪县| 南郑县| 曲阳县| 东山县| 恩平市| 登封市| 夏津县| 南昌市| 蕲春县| 无棣县| 万安县| 宁武县| 张家川|