溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Scala控制結(jié)構(gòu)指的是什么

發(fā)布時(shí)間:2021-11-20 16:42:03 來源:億速云 閱讀:127 作者:柒染 欄目:編程語(yǔ)言

本篇文章給大家分享的是有關(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è)資訊頻道。

向AI問一下細(xì)節(jié)

免責(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)容。

AI