自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從Java走進(jìn)Scala:使用元組、數(shù)組和列表

開(kāi)發(fā) 后端
在 Scala 中,對(duì)象占有一席之地,然而,也經(jīng)常使用到一些函數(shù)類(lèi)型,比如元組、數(shù)組和列表。在這一期由 Ted Neward 撰寫(xiě)的流行系列文章中,您將探究 Scala 中的函數(shù)部分,并且首先研究 Scala 對(duì)函數(shù)語(yǔ)言中常見(jiàn)類(lèi)型的支持。

對(duì)于學(xué)習(xí) Scala 的 Java 開(kāi)發(fā)人員來(lái)說(shuō),對(duì)象是一個(gè)比較自然、簡(jiǎn)單的入口點(diǎn)。在本系列前幾期文章中,我介紹了 Scala 中一些面向?qū)ο蟮木幊谭椒ǎ@些方法實(shí)際上與 Java 編程的區(qū)別不是很大。我還向您展示了 Scala 如何重新應(yīng)用傳統(tǒng)的面向?qū)ο蟾拍?,找到其缺點(diǎn),并根據(jù) 21 世紀(jì)的新需求重新加以改造。Scala 一直隱藏的一些重要內(nèi)容將要現(xiàn)身:Scala 也是一種函數(shù)語(yǔ)言(這里的函數(shù)性是與其他 dys 函數(shù)語(yǔ)言相對(duì)而言的)。

Scala 的面向函數(shù)性非常值得探討,這不僅是因?yàn)橐呀?jīng)研究完了對(duì)象內(nèi)容。Scala 中的函數(shù)編程將提供一些新的設(shè)計(jì)結(jié)構(gòu)和理念以及一些內(nèi)置構(gòu)造,它們使某些場(chǎng)景(例如并發(fā)性)的編程變得非常簡(jiǎn)單。

本月,您將首次進(jìn)入 Scala 的函數(shù)編程領(lǐng)域,查看大多數(shù)函數(shù)語(yǔ)言中常見(jiàn)的四種類(lèi)型:列表(list)、元組(tuple)、集合(set)和 Option 類(lèi)型。您還將了解 Scala 的數(shù)組,后者對(duì)其他函數(shù)語(yǔ)言來(lái)說(shuō)十分新鮮。這些類(lèi)型都提出了編寫(xiě)代碼的新方式。當(dāng)結(jié)合傳統(tǒng)面向?qū)ο筇匦詴r(shí),可以生成十分簡(jiǎn)潔的結(jié)果。

使用 Option(s)

在什么情況下,“無(wú)” 并不代表 “什么也沒(méi)有”?當(dāng)它為 0 的時(shí)候,與 null 有什么關(guān)系。

對(duì)于我們大多數(shù)人都非常熟悉的概念,要在軟件中表示為 “無(wú)” 是一件十分困難的事。例如,看看 C++ 社區(qū)中圍繞 NULL 和 0 進(jìn)行的激烈討論,或是 SQL 社區(qū)圍繞 NULL 列值展開(kāi)的爭(zhēng)論,便可知曉一二。 NULL 或 null 對(duì)于大多數(shù)程序員來(lái)說(shuō)都表示 “無(wú)”,但是這在 Java 語(yǔ)言中引出了一些特殊問(wèn)題。

考慮一個(gè)簡(jiǎn)單操作,該操作可以從一些位于內(nèi)存或磁盤(pán)的數(shù)據(jù)庫(kù)查找程序員的薪資:API 允許調(diào)用者傳入一個(gè)包含程序員名字的 String,這會(huì)返回什么呢?從建模角度來(lái)看,它應(yīng)該返回一個(gè) Int,表示程序員的年薪;但是這里有一個(gè)問(wèn)題,如果程序員不在數(shù)據(jù)庫(kù)中(可能根本沒(méi)有雇用她,或者已經(jīng)被解雇,要不就是輸錯(cuò)了名字……),那么應(yīng)該返回什么。如果返回類(lèi)型是 Int,則不能返回 null,這個(gè) “標(biāo)志” 通常表示沒(méi)有在數(shù)據(jù)庫(kù)中找到該用戶(hù)(您可能認(rèn)為應(yīng)該拋出一個(gè)異常,但是大多數(shù)時(shí)候數(shù)據(jù)庫(kù)丟失值并不能視為異常,因此不應(yīng)該在這里拋出異常)。

在 Java 代碼中,我們最終將方法標(biāo)記為返回 java.lang.Integer,這迫使調(diào)用者知道方法可以返回 null。自然,我們可以依靠程序員來(lái)全面歸檔這個(gè)場(chǎng)景,還可以依賴(lài)程序員讀取 精心準(zhǔn)備的文檔。這類(lèi)似于:我們可以要求經(jīng)理傾聽(tīng)我們反對(duì)他們要求的不可能完成的項(xiàng)目期限,然后經(jīng)理再進(jìn)一步把我們的反對(duì)傳達(dá)給上司和用戶(hù)。

Scala 提供了一種普通的函數(shù)方法,打破了這一僵局。在某些方面,Option 類(lèi)型或 Option[T],并不重視描述。它是一個(gè)具有兩個(gè)子類(lèi) Some[T] 和 None 的泛型類(lèi),用來(lái)表示 “無(wú)值” 的可能性,而不需要語(yǔ)言類(lèi)型系統(tǒng)大費(fèi)周折地支持這個(gè)概念。實(shí)際上,使用 Option[T] 類(lèi)型可以使問(wèn)題更加清晰(下一節(jié)將用到)。

在使用 Option[T] 時(shí),關(guān)鍵的一點(diǎn)是認(rèn)識(shí)到它實(shí)質(zhì)上是一個(gè)大小為 “1” 的強(qiáng)類(lèi)型集合,使用一個(gè)不同的值 None 表示 “nothing” 值的可能性。因此,在這里方法沒(méi)有返回 null 表示沒(méi)有找到數(shù)據(jù),而是進(jìn)行聲明以返回 Option[T],其中 T 是返回的原始類(lèi)型。那么,對(duì)于沒(méi)有查找到數(shù)據(jù)的場(chǎng)景,只需返回 None,如下所示:

清單 1. 準(zhǔn)備好踢足球了嗎?

  1. @Test def simpleOptionTest =  
  2. {  
  3.   val footballTeamsAFCEast =  
  4.     Map("New England" -> "Patriots",  
  5.         "New York" -> "Jets",  
  6.         "Buffalo" -> "Bills",  
  7.         "Miami" -> "Dolphins",  
  8.         "Los Angeles" -> null)  
  9.     
  10.   assertEquals(footballTeamsAFCEast.get("Miami"), Some("Dolphins"))  
  11.   assertEquals(footballTeamsAFCEast.get("Miami").get(), "Dolphins")  
  12.   assertEquals(footballTeamsAFCEast.get("Los Angeles"), Some(null))  
  13.   assertEquals(footballTeamsAFCEast.get("Sacramento"), None)  

注意,Scala Map 中 get 的返回值實(shí)際上并不對(duì)應(yīng)于傳遞的鍵。相反,它是一個(gè) Option[T] 實(shí)例,可以是與某個(gè)值有關(guān)的 Some(),也可以是 None,因此可以很清晰地表示沒(méi)有在 map 中找到鍵。如果它可以表示 map 上存在某個(gè)鍵,但是有對(duì)應(yīng)的 null 值,這一點(diǎn)特別重要了。比如清單 1 中 Los Angeles 鍵。

通常,當(dāng)處理 Option[T] 時(shí),程序員將使用模式匹配,這是一個(gè)非常函數(shù)化的概念,它允許有效地 “啟用” 類(lèi)型和/或值,更不用說(shuō)在定義中將值綁定到變量、在 Some() 和 None 之間切換,以及提取 Some 的值(而不需要調(diào)用麻煩的 get() 方法)。清單 2 展示了 Scala 的模式匹配:

清單 2. 巧妙的模式匹配

  1. @Test def optionWithPM =  
  2. {  
  3.   val footballTeamsAFCEast =  
  4.     Map("New England" -> "Patriots",  
  5.         "New York" -> "Jets",  
  6.         "Buffalo" -> "Bills",  
  7.         "Miami" -> "Dolphins")  
  8.           
  9.   def show(value : Option[String]) =  
  10.   {  
  11.     value match  
  12.     {  
  13.       case Some(x) => x  
  14.       case None => "No team found" 
  15.     }  
  16.   }  
  17.     
  18.   assertEquals(show(footballTeamsAFCEast.get("Miami")), "Dolphins")  

C# 2.0 可變?yōu)?null 值的類(lèi)型

其他語(yǔ)言已試圖通過(guò)各種方法解決 “可 null 值化” 問(wèn)題:C++ 一直都忽略了這個(gè)問(wèn)題,直至最后確定 null 和 0 是不同的值。Java 語(yǔ)言仍然沒(méi)有徹底解決這個(gè)問(wèn)題,而是依賴(lài)于自動(dòng)裝箱(autobox)— 將原語(yǔ)類(lèi)型自動(dòng)轉(zhuǎn)換為它們的包裝器對(duì)象(在 1.1 以后引入)— 幫助 Java 程序員解決問(wèn)題。一些模式愛(ài)好者建議每種類(lèi)型都應(yīng)該有一個(gè)對(duì)應(yīng)的 “Null Object”,即將自己的所有方法重寫(xiě)為不執(zhí)行任何操作的類(lèi)型(實(shí)際上是子類(lèi)型)的實(shí)例 — 實(shí)踐證明這需要大量工作。C# 1.0 發(fā)布后,C# 設(shè)計(jì)者決定采取一種完全不同的方法解決 null 值化問(wèn)題。

C# 2.0 引入了可變?yōu)?null 值的類(lèi)型 的概念,重要的是添加了語(yǔ)法支持,認(rèn)為任何特定值類(lèi)型(基本指原語(yǔ)類(lèi)型)都可以通過(guò)將 null 封裝到一個(gè)泛型/模板類(lèi) Nullable< T>,從而提供 null 支持。Nullable< T> 本身是在類(lèi)型聲明中通過(guò) ? 修飾符號(hào)引入。因此,int? 表示一個(gè)整數(shù)也可能為 null。

表面上看,這似乎很合理,但是事情很快就變得復(fù)雜起來(lái)。int 和 int? 是否應(yīng)該被視為可兼容類(lèi)型,如果是的話(huà),什么時(shí)候?qū)?int 提升為 int?,反之呢?當(dāng)將 int 添加到 int? 會(huì)發(fā)生什么,結(jié)果會(huì)是 null 嗎?這類(lèi)問(wèn)題等等。隨后類(lèi)型系統(tǒng)進(jìn)行了一些重要的調(diào)整,可變?yōu)?null 值的類(lèi)型隨后包含到了 2.0 中 — 而 C# 程序員幾乎完全忽略了它們。

回顧一下 Option 類(lèi)型的函數(shù)方法,它使 Option[T] 和 Int 之間的界限變得很清晰,看上去要比其他方法更加簡(jiǎn)單。在那些圍繞可變?yōu)?null 值類(lèi)型的反直覺(jué)(counterintuitive)提升規(guī)則之間進(jìn)行比較時(shí),尤其如此。(函數(shù)領(lǐng)域?qū)υ搯?wèn)題近二十年的思考是值得的)。要使用 Option[T] 必須付出一些努力,但是總的來(lái)說(shuō),它產(chǎn)生了更清晰的代碼和期望。

#p#

元組和集合

在 C++ 中,我們將之稱(chēng)為結(jié)構(gòu)體。在 Java 編程中,我們稱(chēng)之為數(shù)據(jù)傳輸對(duì)象或參數(shù)對(duì)象。在 Scala 中,我們稱(chēng)為元組。實(shí)質(zhì)上,它們是一些將其他數(shù)據(jù)類(lèi)型收集到單個(gè)實(shí)例的類(lèi),并且不使用封裝或抽象 — 實(shí)際上,不 使用任何抽象常常更有用。

在 Scala 創(chuàng)建一個(gè)元組類(lèi)型非常的簡(jiǎn)單,這只是主體的一部分:如果首先將元素公開(kāi)給外部,那么在類(lèi)型內(nèi)部創(chuàng)建描述這些元素的名稱(chēng)就毫無(wú)價(jià)值??紤]清單 3:

清單 3. tuples.scala

  1. // JUnit test suite  
  2. //  
  3. class TupleTest  
  4. {  
  5.   import org.junit._, Assert._  
  6.   import java.util.Date  
  7.    
  8.   @Test def simpleTuples() =  
  9.   {  
  10.     val tedsStartingDateWithScala = Date.parse("3/7/2006")  
  11.  
  12.     val tuple = ("Ted""Scala", tedsStartingDateWithScala)  
  13.       
  14.     assertEquals(tuple._1, "Ted")  
  15.     assertEquals(tuple._2, "Scala")  
  16.     assertEquals(tuple._3, tedsStartingDateWithScala)  
  17.   }  
  18. }  

創(chuàng)建元組非常簡(jiǎn)單,將值放入一組圓括號(hào)內(nèi),就好象調(diào)用一個(gè)方法調(diào)用一樣。提取這些值只需要調(diào)用 “_n” 方法,其中 n 表示相關(guān)的元組元素的位置參數(shù):_1 表示第一位,_2 表示第二位,依此類(lèi)推。傳統(tǒng)的 Java java.util.Map 實(shí)質(zhì)上是一個(gè)分兩部分的元組集合。

元組可以輕松地實(shí)現(xiàn)使用單個(gè)實(shí)體移動(dòng)多個(gè)值,這意味著元組可以提供在 Java 編程中非常重量級(jí)的操作:多個(gè)返回值。例如,某個(gè)方法可以計(jì)算 String 中字符的數(shù)量,并返回該 String 中出現(xiàn)次數(shù)最多的字符,但是如果程序員希望同時(shí) 返回最常出現(xiàn)的字符和 它出現(xiàn)的次數(shù),那么程序設(shè)計(jì)就有點(diǎn)復(fù)雜了:或是創(chuàng)建一個(gè)包含字符及其出現(xiàn)次數(shù)的顯式類(lèi),或?qū)⒅底鳛樽侄伪4娴綄?duì)象中并在需要時(shí)返回字段值。無(wú)論使用哪種方法,與使用 Scala 相比,都需要編寫(xiě)大量代碼;通過(guò)簡(jiǎn)單地返回包含字符及其出現(xiàn)次數(shù)的元組,Scala 不僅可以輕松地使用 “_1”、“_2” 等訪問(wèn)元組的各個(gè)值,還可以輕松地返回多個(gè)返回值。

如下節(jié)所示,Scala 頻繁地將 Option 和元組保存到集合(例如 Array[T] 或列表)中,從而通過(guò)一個(gè)比較簡(jiǎn)單的結(jié)構(gòu)提供了極大的靈活性和威力。

數(shù)組帶您走出陰霾

讓我們重新審視一個(gè)老朋友 — 數(shù)組 — 在 Scala 中是 Array[T]。和 Java 代碼中的數(shù)組一樣,Scala 的 Array[T] 是一組有序的元素序列,使用表示數(shù)組位置的數(shù)值進(jìn)行索引,并且該值不可以超過(guò)數(shù)組的總大小,如清單 4 所示:

清單 4. array.scala

  1. object ArrayExample1  
  2. {  
  3.   def main(args : Array[String]) : Unit =  
  4.   {  
  5.     for (i <- 0 to args.length-1)  
  6.     {  
  7.       System.out.println(args(i))  
  8.     }  
  9.   }  

盡管等同于 Java 代碼中的數(shù)組(畢竟后者是最終的編譯結(jié)果),Scala 中的數(shù)組使用了截然不同的定義。對(duì)于新手,Scala 中的數(shù)組實(shí)際上就是泛型類(lèi),沒(méi)有增加 “內(nèi)置” 狀態(tài)(至少,不會(huì)比 Scala 庫(kù)附帶的其他類(lèi)多)。例如,在 Scala 中,數(shù)組一般定義為 Array[T] 的實(shí)例,這個(gè)類(lèi)定義了一些額外的有趣方法,包括常見(jiàn)的 “l(fā)ength” 方法,它將返回?cái)?shù)組的長(zhǎng)度。因此,在 Scala 中,可以按照傳統(tǒng)意義使用 Array,例如使用 Int 在 0 到 args.length - 1 間進(jìn)行迭代,并獲取數(shù)組的第 i 個(gè)元素(使用圓括號(hào)而不是方括號(hào)來(lái)指定返回哪個(gè)元素,這是另一種名稱(chēng)比較有趣的方法)。 
 
擴(kuò)展數(shù)組

事實(shí)證明 Array 擁有大量方法,這些方法繼承自一個(gè)非常龐大的 parent 層次結(jié)構(gòu):Array 擴(kuò)展 Array0,后者擴(kuò)展 ArrayLike[A],ArrayLike[A] 擴(kuò)展 Mutable[A],Mutable[A] 又?jǐn)U展 RandomAccessSeq[A],RandomAccessSeq[A] 擴(kuò)展了 Seq[A],等等。實(shí)際上,這種層次結(jié)構(gòu)意味著 Array 可以執(zhí)行很多操作,因此與 Java 編程相比,在 Scala 中可以更輕松地使用數(shù)組。

例如,如清單 4 所示,使用 foreach 方法遍歷數(shù)組更加簡(jiǎn)單并且更貼近函數(shù)的方式,這些都繼承自 Iterable 特性:

清單 5. ArrayExample2

  1. object   
  2. {  
  3.   def main(args : Array[String]) : Unit =  
  4.   {  
  5.     args.foreach( (arg) => System.out.println(arg) )  
  6.   }  

看上去您沒(méi)有節(jié)省多少工作,但是,將一個(gè)函數(shù)(匿名或其他)傳入到另一個(gè)類(lèi)中以便獲得在特定語(yǔ)義下(在本例中指遍歷數(shù)組)執(zhí)行的能力,是函數(shù)編程的常見(jiàn)主題。以這種方式使用更高階函數(shù)并不局限于迭代;事實(shí)上,還得經(jīng)常對(duì)數(shù)組內(nèi)容執(zhí)行一些過(guò)濾 操作去掉無(wú)用的內(nèi)容,然后再處理結(jié)果。例如,在 Scala 中,可以輕松地使用 filter 方法進(jìn)行過(guò)濾,然后獲取結(jié)果列表并使用 map 和另一個(gè)函數(shù)(類(lèi)型為 (T) => U,其中 T 和 U 都是泛型類(lèi)型),或 foreach 來(lái)處理每個(gè)元素。我在清單 6 中采取了后一種方法(注意 filter 使用了一個(gè) (T) : Boolean 方法,意味著使用數(shù)組持有的任意類(lèi)型的參數(shù),并返回一個(gè) Boolean)。

清單 6. 查找所有 Scala 程序員

  1. class ArrayTest  
  2. {  
  3.   import org.junit._, Assert._  
  4.     
  5.   @Test def testFilter =  
  6.   {  
  7.     val programmers = Array(  
  8.         new Person("Ted""Neward"3750000,  
  9.           Array("C++""Java""Scala""Groovy""C#""F#""Ruby")),  
  10.         new Person("Amanda""Laucher"2745000,  
  11.           Array("C#""F#""Java""Scala")),  
  12.         new Person("Luke""Hoban"3245000,  
  13.           Array("C#""Visual Basic""F#")),  
  14.   new Person("Scott""Davis"4050000,  
  15.     Array("Java""Groovy"))  
  16.       )  
  17.  
  18.     // 查找所有Scala程序員 ...  
  19.     val scalaProgs =  
  20.       programmers.filter((p) => p.skills.contains("Scala") )  
  21.       
  22.     // 應(yīng)該只有2  
  23.     assertEquals(2, scalaProgs.length)  
  24.       
  25.     // ... now perform an operation on each programmer in the resulting  
  26.     // array of Scala programmers (give them a raise, of course!)  
  27.     //  
  28.     scalaProgs.foreach((p) => p.salary += 5000)  
  29.       
  30.     // Should each be increased by 5000 ...  
  31.     assertEquals(programmers(0).salary, 50000 + 5000)  
  32.     assertEquals(programmers(1).salary, 45000 + 5000)  
  33.       
  34.     // ... except for our programmers who don't know Scala  
  35.     assertEquals(programmers(2).salary, 45000)  
  36.  assertEquals(programmers(3).salary, 50000)  
  37.   }  
  38. }  

創(chuàng)建一個(gè)新的 Array 時(shí)將用到 map 函數(shù),保持原始的數(shù)組內(nèi)容不變,實(shí)際上大多數(shù)函數(shù)性程序員都喜歡這種方式:

清單 7. Filter 和 map

  1. @Test def testFilterAndMap =  
  2. {  
  3.   val programmers = Array(  
  4.       new Person("Ted""Neward"3750000,  
  5.         Array("C++""Java""Scala""C#""F#""Ruby")),  
  6.       new Person("Amanda""Laucher"2745000,  
  7.         Array("C#""F#""Java""Scala")),  
  8.       new Person("Luke""Hoban"3245000,  
  9.         Array("C#""Visual Basic""F#"))  
  10. new Person("Scott""Davis"4050000,  
  11.   Array("Java""Groovy"))  
  12.     )  
  13.  
  14.   // Find all the Scala programmers ...  
  15.   val scalaProgs =  
  16.     programmers.filter((p) => p.skills.contains("Scala") )  
  17.     
  18.   // Should only be 2  
  19.   assertEquals(2, scalaProgs.length)  
  20.     
  21.   // ... now perform an operation on each programmer in the resulting  
  22.   // array of Scala programmers (give them a raise, of course!)  
  23.   //  
  24.   def raiseTheScalaProgrammer(p : Person) =  
  25.   {  
  26.     new Person(p.firstName, p.lastName, p.age,  
  27.       p.salary + 5000, p.skills)  
  28.   }  
  29.   val raisedScalaProgs =   
  30.     scalaProgs.map(raiseTheScalaProgrammer)  
  31.     
  32.   assertEquals(2, raisedScalaProgs.length)  
  33.   assertEquals(50000 + 5000, raisedScalaProgs(0).salary)  
  34.   assertEquals(45000 + 5000, raisedScalaProgs(1).salary)  
  35. }  

注意,在清單 7 中,Person 的 salary 成員可以標(biāo)記為 “val”,表示不可修改,而不是像上文一樣為了修改不同程序員的薪資而標(biāo)記為 “var”。

Scala 的 Array 提供了很多方法,在這里無(wú)法一一列出并演示??偟膩?lái)說(shuō),在使用數(shù)組時(shí),應(yīng)該充分地利用 Array 提供的方法,而不是使用傳統(tǒng)的 for ... 模式遍歷數(shù)組并查找或執(zhí)行需要的操作。最簡(jiǎn)單的實(shí)現(xiàn)方法通常是編寫(xiě)一個(gè)函數(shù)(如果有必要的話(huà)可以使用嵌套,如清單 7 中的 testFilterAndMap 示例所示),這個(gè)函數(shù)可以執(zhí)行所需的操作,然后根據(jù)期望的結(jié)果將該函數(shù)傳遞給 Array 中的 map、filter、foreach 或其他方法之一。

#p#

函數(shù)性列表

函數(shù)編程多年來(lái)的一個(gè)核心特性就是列表,它和數(shù)組在對(duì)象領(lǐng)域中享有相同級(jí)別的 “內(nèi)置” 性。列表對(duì)于構(gòu)建函數(shù)性軟件非常關(guān)鍵,因此,您(作為一名剛起步的 Scala 程序員)必須能夠理解列表及其工作原理。即使列表從未形成新的設(shè)計(jì),但是 Scala 代碼在其庫(kù)中廣泛使用了列表。因此學(xué)習(xí)列表是非常必要的。

在 Scala 中,列表類(lèi)似于數(shù)組,因?yàn)樗暮诵亩x是 Scala 庫(kù)中的標(biāo)準(zhǔn)類(lèi) List[T]。并且,和 Array[T] 相同,List[T] 繼承了很多基類(lèi)和特性,首先使用 Seq[T] 作為直接上層基類(lèi)。

基本上,列表是一些可以通過(guò)列表頭或列表尾提取的元素的集合。列表來(lái)自于 Lisp,后者是一種主要圍繞 “LISt 處理” 的語(yǔ)言,它通過(guò) car 操作獲得列表的頭部,通過(guò) cdr 操作獲得列表尾部(名稱(chēng)淵源與歷史有關(guān);第一個(gè)可以解釋它的人有獎(jiǎng)勵(lì))。

從很多方面來(lái)講,使用列表要比使用數(shù)組簡(jiǎn)單,原因有二,首先函數(shù)語(yǔ)言過(guò)去一直為列表處理提供了良好的支持(而 Scala 繼承了這些支持),其次可以很好地構(gòu)成和分解列表。例如,函數(shù)通常從列表中挑選內(nèi)容。為此,它將選取列表的第一個(gè)元素 — 列表頭部 — 來(lái)對(duì)該元素執(zhí)行處理,然后再遞歸式地將列表的其余部分傳遞給自身。這樣可以極大減少處理代碼內(nèi)部具有相同共享狀態(tài)的可能性,并且,假如每個(gè)步驟只需處理一個(gè)元素,極有可能使代碼分布到多個(gè)線(xiàn)程(如果處理是比較好的)。

構(gòu)成和分解列表非常簡(jiǎn)單,如清單 8 所示:

清單 8. 使用列表

  1. class ListTest  
  2. {  
  3.   import org.junit._, Assert._  
  4.     
  5.   @Test def simpleList =  
  6.   {  
  7.     val myFirstList = List("Ted""Amanda""Luke")  
  8.       
  9.     assertEquals(myFirstList.isEmpty, false)  
  10.     assertEquals(myFirstList.head, "Ted")  
  11.     assertEquals(myFirstList.tail, List("Amanda""Luke")  
  12.     assertEquals(myFirstList.last, "Luke")  
  13.   }  
  14. }  

注意,構(gòu)建列表與構(gòu)建數(shù)組十分相似;都類(lèi)似于構(gòu)建一個(gè)普通對(duì)象,不同之處是這里不需要 “new”(這是 “case 類(lèi)” 的功能,我們將在未來(lái)的文章中介紹到)。請(qǐng)進(jìn)一步注意 tail 方法調(diào)用的結(jié)果 — 結(jié)果并不是列表的最后一個(gè)元素(通過(guò) last 提供),而是除第一個(gè)元素以外的其余列表元素。

當(dāng)然,列表的強(qiáng)大力量部分來(lái)自于遞歸處理列表元素的能力,這表示可以從列表提取頭部,直到列表為空,然后累積結(jié)果:

清單 9. 遞歸處理 

  1. @Test def recurseList =  
  2. {  
  3.   val myVIPList = List("Ted""Amanda""Luke""Don""Martin")  
  4.     
  5.   def count(VIPs : List[String]) : Int =  
  6.   {  
  7.     if (VIPs.isEmpty)  
  8.       0 
  9.     else 
  10.       count(VIPs.tail) + 1 
  11.   }  
  12.     
  13.   assertEquals(count(myVIPList), myVIPList.length)  

注意,如果不考慮返回類(lèi)型 count,Scala 編譯器或解釋器將會(huì)出現(xiàn)點(diǎn)麻煩 — 因?yàn)檫@是一個(gè)尾遞歸(tail-recursive)調(diào)用,旨在減少在大量遞歸操作中創(chuàng)建的棧幀的數(shù)量,因此需要指定它的返回類(lèi)型。即使是這樣,也可以輕松地使用 List 的 “l(fā)ength” 成員獲取列表項(xiàng)的數(shù)量,但關(guān)鍵是如何解釋列表處理強(qiáng)大的功能。清單 9 中的整個(gè)方法完全是線(xiàn)程安全的,因?yàn)榱斜硖幚碇惺褂玫恼麄€(gè)中間狀態(tài)保存在參數(shù)的堆棧上。因此,根據(jù)定義,它不能被多個(gè)線(xiàn)程訪問(wèn)。函數(shù)性方法的一個(gè)優(yōu)點(diǎn)就是它實(shí)際上與程序功能截然不同,并且仍然創(chuàng)建共享的狀態(tài)。

列表 API

列表具有另外一些有趣的特性,例如構(gòu)建列表的替代方法,使用 :: 方法(是的,這是一種方法。只不過(guò)名稱(chēng)比較有趣)。因此,不必使用 “List” 構(gòu)造函數(shù)語(yǔ)法構(gòu)建列表,而是將它們 “拼接” 在一起(在調(diào)用 :: 方法時(shí)),如清單 10 所示:

清單 10. 是 :: == C++ 嗎?

  1. @Test def recurseConsedList =  
  2. {  
  3.   val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil  
  4.     
  5.   def count(VIPs : List[String]) : Int =  
  6.   {  
  7.     if (VIPs.isEmpty)  
  8.       0 
  9.     else 
  10.       count(VIPs.tail) + 1 
  11.   }  
  12.     
  13.   assertEquals(count(myVIPList), myVIPList.length)  

在使用 :: 方法時(shí)要小心 — 它引入了一些很有趣的規(guī)則。它的語(yǔ)法在函數(shù)語(yǔ)言中非常常見(jiàn),因此 Scala 的創(chuàng)建者選擇支持這種語(yǔ)法,但是要正確、普遍地使用這種語(yǔ)法,必須使用一種比較古怪的規(guī)則:任何以冒號(hào)結(jié)束的 “名稱(chēng)古怪的方法” 都是右關(guān)聯(lián)(right-associative)的,這表示整個(gè)表達(dá)式從它的最右邊的 Nil 開(kāi)始,它正好是一個(gè) List。因此,可以將 :: 認(rèn)定為一個(gè)全局的 :: 方法,與 String 的一個(gè)成員方法(本例中使用)相對(duì);這又表示您可以對(duì)所有內(nèi)容構(gòu)建列表。在使用 :: 時(shí),最右邊的元素必須是一個(gè)列表,否則將得到一個(gè)錯(cuò)誤消息。

#p#

什么是右關(guān)聯(lián)?

要更好地理解 :: 方法,要記住 “冒號(hào)” 這類(lèi)操作符僅僅是一些名稱(chēng)比較有趣的方法。對(duì)于普通的左管理語(yǔ)法,左側(cè)的標(biāo)記一般是我將要對(duì)其調(diào)用方法名(右側(cè)的標(biāo)記)的對(duì)象。因此,通常來(lái)說(shuō),表達(dá)式 1 + 2 在編譯器看來(lái)等同于 1.+(2)。

但是對(duì)于列表而言,這些都不適合 — 系統(tǒng)中的每個(gè)類(lèi)都需要對(duì)系統(tǒng)中的所有類(lèi)型使用 :: 方法,而這嚴(yán)重違背了關(guān)注點(diǎn)分離原則。

Scala 的修復(fù)方法是:以冒號(hào)結(jié)束的任何具有奇怪名稱(chēng)的方法(例如 :: 或 :::,甚至是我自己創(chuàng)建的方法,比如 foo:)都是右關(guān)聯(lián)的。因此,比方說(shuō),a :: b :: c :: Nil 轉(zhuǎn)換為 Nil.::(c.::(b.::(a))),后者正是我需要的:List 在首位,這樣每次調(diào)用 :: 都可以獲取對(duì)象參數(shù)并返回一個(gè) List,并繼續(xù)執(zhí)行下去。

最好為其他命名約定指定右關(guān)聯(lián)屬性,但是在撰寫(xiě)本文之際,Scala 已將這條規(guī)則硬編碼到該語(yǔ)言中。就目前來(lái)說(shuō),冒號(hào)是惟一觸發(fā)右關(guān)聯(lián)行為的字符。
 
在 Scala 中,列表的一種最強(qiáng)大的用法是與模式匹配結(jié)合。由于列表不僅可以匹配類(lèi)型和值,它還可以同時(shí)綁定變量。例如,我可以簡(jiǎn)化清單 10 的列表代碼,方法是使用模式匹配區(qū)別一個(gè)至少具有一個(gè)元素的列表和一個(gè)空列表:

清單 11. 結(jié)合使用模式匹配和列表

  1. @Test def recurseWithPM =  
  2. {  
  3.   val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil  
  4.     
  5.   def count(VIPs : List[String]) : Int =  
  6.   {  
  7.     VIPs match  
  8.     {  
  9.       case h :: t => count(t) + 1 
  10.       case Nil => 0 
  11.     }  
  12.   }  
  13.     
  14.   assertEquals(count(myVIPList), myVIPList.length)  

在第一個(gè) case 表達(dá)式中,將提取列表頭部并綁定到變量 h,而其余部分(尾部)則綁定到 t;在本例中,沒(méi)有對(duì) h 執(zhí)行任何操作(實(shí)際上,更好的方法是指明這個(gè)頭部永遠(yuǎn)不會(huì)被使用,方法是使用一個(gè)通配符 _ 代替 h,這表明它是永遠(yuǎn)不會(huì)使用到的變量的占位符)。但是 t 被遞歸地傳遞給 count,和前面的示例一樣。還要注意,Scala 中的每一個(gè)表達(dá)式將隱式返回一個(gè)值;在本例中,模式匹配表達(dá)式的結(jié)果是遞歸調(diào)用 count + 1,當(dāng)達(dá)到列表結(jié)尾時(shí),結(jié)果為 0。

考慮到相同的代碼量,使用模式匹配的價(jià)值體現(xiàn)在哪里?實(shí)際上,對(duì)于比較簡(jiǎn)單的代碼,模式匹配的價(jià)值不很明顯。但是對(duì)于稍微復(fù)雜的代碼,例如擴(kuò)展示例以匹配特定值,那么模式匹配非常有幫助。

清單 12. 模式匹配

  1. @Test def recurseWithPMAndSayHi =  
  2. {  
  3.   val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil  
  4.     
  5.   var foundAmanda = false 
  6.   def count(VIPs : List[String]) : Int =  
  7.   {  
  8.     VIPs match  
  9.     {  
  10.       case "Amanda" :: t =>  
  11.         System.out.println("Hey, Amanda!"); foundAmanda = true; count(t) + 1 
  12.       case h :: t =>  
  13.         count(t) + 1 
  14.       case Nil =>  
  15.         0 
  16.     }  
  17.   }  
  18.     
  19.   assertEquals(count(myVIPList), myVIPList.length)  
  20.   assertTrue(foundAmanda)  

示例很快會(huì)變得非常復(fù)雜,特別是正則表達(dá)式或 XML 節(jié)點(diǎn),開(kāi)始大量使用模式匹配方法。模式匹配的使用同樣不局限于列表;我們沒(méi)有理由不把它擴(kuò)展到前面的數(shù)組示例中。事實(shí)上,以下是前面的 recurseWithPMAndSayHi 測(cè)試的數(shù)組示例:

清單 13. 將模式匹配擴(kuò)展到數(shù)組

  1. @Test def recurseWithPMAndSayHi =  
  2. {  
  3.   val myVIPList = Array("Ted""Amanda""Luke""Don""Martin")  
  4.  
  5.   var foundAmanda = false 
  6.     
  7.   myVIPList.foreach((s) =>  
  8.     s match  
  9.     {  
  10.       case "Amanda" =>  
  11.         System.out.println("Hey, Amanda!")  
  12.         foundAmanda = true 
  13.       case _ =>  
  14.         ; // Do nothing  
  15.     }  
  16.   )  
  17.  
  18.   assertTrue(foundAmanda)  
  19. }  

如果希望進(jìn)行實(shí)踐,那么嘗試構(gòu)建清單 13 的遞歸版本,但這不用在 recurseWithPMAndSayHi 范圍內(nèi)聲明一個(gè)可修改的 var。提示:需要使用多個(gè)模式匹配代碼塊(本文的 代碼下載 中包含了一個(gè)解決方案 — 但是建議您在查看之前首先自己進(jìn)行嘗試)。

結(jié)束語(yǔ)

Scala 是豐富的集合的集合(雙關(guān)語(yǔ)),這源于它的函數(shù)歷史和特性集;元組提供了一種簡(jiǎn)單的方法,可以很容易地收集松散綁定的值集合;Option[T] 可以使用簡(jiǎn)單的方式表示和 “no” 值相對(duì)的 “some” 值;數(shù)組可以通過(guò)增強(qiáng)的特性訪問(wèn)傳統(tǒng)的 Java 式的數(shù)組語(yǔ)義;而列表是函數(shù)語(yǔ)言的主要集合,等等。

然而,需要特別注意其中一些特性,特別是元組:學(xué)會(huì)使用元組很容易,并且會(huì)因?yàn)闉榱酥苯邮褂迷M而忘記傳統(tǒng)的基本對(duì)象建模。如果某個(gè)特殊元組 — 例如,名稱(chēng)、年齡、薪資和已知的編程語(yǔ)言列表 — 經(jīng)常出現(xiàn)在代碼庫(kù)中,那么將它建模為正常的類(lèi)類(lèi)型和對(duì)象。

Scala 的優(yōu)點(diǎn)是它兼具函數(shù)性和 面向?qū)ο筇匦?,因此,您可以在享?Scala 的函數(shù)類(lèi)型的同時(shí),繼續(xù)像以前一樣關(guān)注類(lèi)設(shè)計(jì)。

【相關(guān)閱讀】

  1. Scala編程語(yǔ)言專(zhuān)題
  2. 面向Java開(kāi)發(fā)人員的Scala指南:當(dāng)繼承中的對(duì)象遇到函數(shù)
  3. 面向Java開(kāi)發(fā)人員的Scala指南:使用Scala版本的Java接口
  4. 面向Java開(kāi)發(fā)人員的Scala指南:Scala控制結(jié)構(gòu)內(nèi)部揭密
  5. 面向Java開(kāi)發(fā)人員的Scala指南:理解Scala的類(lèi)語(yǔ)法和語(yǔ)義
責(zé)任編輯:yangsai 來(lái)源: IBMDW
相關(guān)推薦

2009-09-28 11:01:39

從Java走進(jìn)Scal

2009-02-04 17:32:03

ibmdwJavaScala

2009-08-21 16:17:25

ScalaTwitter API

2009-06-16 17:54:38

Scala類(lèi)語(yǔ)法語(yǔ)義

2009-06-17 11:44:22

Scala控制結(jié)構(gòu)

2009-06-19 10:51:39

Scalapackage訪問(wèn)修飾符

2009-07-15 10:14:25

Scala并發(fā)性

2009-12-09 09:15:47

從Java走進(jìn)ScalTwitter API

2009-08-14 11:35:01

Scala Actor

2009-10-14 11:14:38

ScitterScalaTwitter

2009-06-16 17:09:17

Scala面向?qū)ο?/a>函數(shù)編程

2009-06-19 11:13:47

Scalacase類(lèi)模式匹配

2009-06-17 13:26:06

scala繼承模型

2023-06-30 17:56:31

Scala元組

2009-06-19 13:16:36

Scala計(jì)算器解析器組合子

2009-06-19 11:42:09

Scala計(jì)算器解析

2021-01-13 05:18:50

數(shù)據(jù)類(lèi)型性能

2010-07-20 13:02:08

Perl數(shù)組

2009-07-09 00:25:00

ScalaListTuple

2009-07-09 00:25:00

ScalaSet類(lèi)Map類(lèi)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)