官术网_书友最值得收藏!

7.3 for表達式

Scala的for表達式是用于迭代的“瑞士軍刀”,可以讓你以不同的方式組合一些簡單的因子來表達各式各樣的迭代。它可以幫助我們處理諸如遍歷整數序列的常見任務,也可以通過更高級的表達式來遍歷多個不同種類的集合,并根據任意條件過濾元素,生成新的集合。

遍歷集合

for表達式能做的最簡單的事是,遍歷某個集合的所有元素。例如,示例7.5展示了一組打印出當前目錄所有文件的代碼。I/O操作用到了Java API。首先對當前目錄(".")創建一個java.io.File對象,然后調用它的listFiles方法。這個方法返回一個包含File對象的數組,這些對象分別對應當前目錄中的每個子目錄或文件。最后將結果數組保存在變量filesHere中。

示例7.5 用for表達式列舉目錄中的文件清單

通過“file <- filesHere”這樣的生成器generator)語法,我們將遍歷filesHere的元素。每進行一次迭代,一個新的名稱為fileval都會被初始化成一個元素的值。編譯器推斷出文件的類型為File,這是因為filesHere是一個Array[File]。每進行一次迭代,for表達式的代碼體——println(file),就被執行一次。由于FiletoString方法會返回文件或目錄的名稱,因此這段代碼將會打印出當前目錄的所有文件和子目錄。

for表達式的語法可以用于任何種類的集合,而不僅僅是數組。[3]Range(區間)是一類特殊的用例,在表5.4(93頁)中簡略地提到過??梢杂谩?span id="hbl2zvv" class="code-span">1 to 5”這樣的語法來創建Range,并用for表達式來遍歷它。下面是一個簡單的例子:

如果你不想在被遍歷的值中包含區間的上界,則可以用until而不是to

在Scala中像這樣遍歷整數是常見的做法,不過與其他語言相比,要少一些。在其他語言中,你可能會通過遍歷整數來遍歷數組,就像這樣:

這個for表達式引入了一個變量i,依次將0filesHere.length - 1之間的每個整數值賦值給它。每次對i賦值后,filesHere的第i個元素都會被提取出來做相應的處理。

在Scala中,這類遍歷方式不那么常見的原因是可以直接遍歷集合。這樣做了以后,你的代碼會更短,也避免了很多在遍歷數組時會遇到的偏一位off-by-one)的錯誤。應該以0還是1開始?應該對最后一個下標后加上-1、+1,還是什么都不加?這些疑問很容易回答,但也很容易答錯。完全避免這些問題無疑是更安全的做法。

過濾

有時你并不想完整地遍歷集合,但你想把它過濾成一個子集。這時你可以給for表達式添加過濾器(filter)。過濾器是for表達式的圓括號中的一個if子句。舉例來說,示例7.6的代碼僅列出當前目錄中以“.scala”結尾的那些文件:

示例7.6 用帶過濾器的for表達式查找.scala文件

也可以用如下代碼達到同樣的目的:

這段代碼與前一段代碼產生的輸出沒有區別,可能看上去對于有指令式編程背景的程序員來說更為熟悉。這種指令式編程的代碼風格只是一種選項[4],因為這個特定的for表達式被用作打印的副作用,其結果是單元值()。稍后你將看到,for表達式之所以被稱為“表達式”,是因為它能返回有意義的值,即一個類型可以由for表達式的<-子句決定的集合。

若想隨意包含更多的過濾器,則直接添加if子句即可。例如,為了讓代碼具備額外的防御性,示例7.7的代碼只輸出文件名,不輸出目錄名。實現方式是添加一個檢查文件的isFile方法的過濾器。

示例7.7 在for表達式中使用多個過濾器

嵌套迭代

如果你添加多個<-子句,將得到嵌套的“循環”。例如,示例7.8中的for表達式有兩個嵌套迭代。外部循環遍歷filesHere,內部循環遍歷每個以.scala結尾的文件的fileLines(file)

示例7.8 在for表達式中使用多個生成器

中途(mid-stream)變量綁定

你大概已經注意到,示例7.8中line.trim被重復了兩遍。這并不是一個很無謂的計算,因此你可能想最好只算一次??梢杂玫忍枺?span id="t2ig2hq" class="code-span">=)將表達式的結果綁定到新的變量上。被綁定的這個變量在引入和使用時都與val一樣,只不過去掉了val關鍵字。示例7.9給出了一個例子。

在示例7.9中,for表達式在中途引入了名稱為trimmed的變量。這個變量被初始化為line.trim的結果。for表達式余下的部分則兩次用到了這個新的變量,一次在if表達式中,另一次在println中。

示例7.9 在for表達式中使用中途賦值

交出一個新的集合

雖然目前為止所有示例都是對遍歷到的值進行操作然后忽略它,但是完全可以在每次迭代中生成一個可以被記住的值。具體做法就像我們在第3章的第12步介紹的,在for表達式的代碼體之前加上關鍵字yield而不是do。例如,如下函數識別出.scala文件并將它保存在數組中:

for表達式的代碼體每次被執行,都會交出一個值,本例中就是file。當for表達式執行完畢后,其結果將包含所有交出的值,且被包含在一個集合中。結果集合的類型基于迭代子句中處理的集合種類。在本例中,結果是Array[File],因為filesHere是一個數組,而交出的表達式類型為File。

下面再看一個例子,示例7.10中的for表達式先將包含當前目錄所有文件的名稱為filesHereArray[File]轉換成一個只包含.scala文件的數組。對于每一個文件,再使用fileLines方法(參見示例7.8)的結果生成一個Iterator[String]Iterator提供的nexthasNext方法,可以用來遍歷集合中的元素。這個初始的迭代器又被轉換成另一個Iterator[String],這一次只包含那些包含子串"for"的被去邊的字符串。最后,對這些字符串再交出其長度的整數。這個for表達式的結果是包含這些長度整數的Array[Int]

示例7.10 用for表達式將Array[File]轉換成Array[Int]

至此,你已經看到了Scala的for表達式的所有主要功能特性,不過我們講得比較快。有關for表達式更完整的講解請參考《Scala高級編程》。

主站蜘蛛池模板: 台北市| 清水河县| 酉阳| 奇台县| 昌乐县| 武强县| 咸丰县| 天峻县| 皮山县| 晋江市| 普安县| 松桃| 清镇市| 防城港市| 惠水县| 房产| 寻甸| 和政县| 南郑县| 临海市| 大连市| 大兴区| 札达县| 海口市| 海城市| 昂仁县| 辽阳县| 平顶山市| 新野县| 临城县| 建水县| 闽清县| 于都县| 凤阳县| 永泰县| 奎屯市| 枝江市| 离岛区| 江阴市| 长寿区| 正定县|