Scala編程實(shí)例:使用Set和Map
因?yàn)镾cala致力于幫助你充分利用函數(shù)式和指令式風(fēng)格兩方面的好處,它的集合類型庫(kù)于是就區(qū)分了集合類的可變和不可變。例如,數(shù)組始終是可變的,而列表始終不可變。當(dāng)問(wèn)題討論到集和映射,Scala同樣提供了可變和不可變的替代品,不過(guò)用了不同的辦法。對(duì)于集和映射,Scala把可變性建模在類繼承中。
51CTO編輯推薦:Scala編程語(yǔ)言專題
例如,Scala的API包含了集的一個(gè)基本特質(zhì):trait,特質(zhì)這個(gè)概念接近于Java的接口。Scala于是提供了兩個(gè)子特質(zhì),一個(gè)是可變的集,另一個(gè)是不可變的集。就如你在圖3.2里會(huì)看到的,這三個(gè)特質(zhì)都共享同樣的簡(jiǎn)化名,Set。然而它們的全稱不一樣,因?yàn)槊總€(gè)都放在不同的包里。Scala的API里具體的Set類,如圖3.2的HashSet類,擴(kuò)展了要么是可變的,要么不可變的Set特質(zhì)。(盡管Java里面稱為“實(shí)現(xiàn)”了接口,在Scala里面稱為“擴(kuò)展”或“混入”了特質(zhì)。)因此,如果你想要使用HashSet,你可以根據(jù)你的需要選擇可變的或不可變的變體。創(chuàng)造集的缺省方法展示在代碼3.5中:
- var jetSet = Set("Boeing", "Airbus")
- jetSet += "Lear"
- println(jetSet.contains("Cessna"))
代碼 3.5 創(chuàng)造,初始化,和使用不可變集
代碼3.5的第一行代碼里,定義了名為jetSet的新var,并使用了包含兩個(gè)字串,"Boeing"和"Airbus"的不可變集完成了初始化。就像例子中展示的,Scala中創(chuàng)建集的方法與創(chuàng)建列表和數(shù)組的類似:通過(guò)調(diào)用Set伴生對(duì)象的名為apply的工廠方法。代碼3.5中,對(duì)scala.collection.immutable.Set的伴生對(duì)象調(diào)用了apply方法,返回了一個(gè)缺省的,不可變Set的實(shí)例。Scala編譯器推斷jetSet的類型為不可變Set[String]。
要向集加入新的變量,可以在集上調(diào)用+,傳入新的元素??勺兊暮筒豢勺兊募继峁┝?方法,但它們的行為不同??勺兗瘜言丶尤胱陨恚豢勺兗瘜?chuàng)建并返回一個(gè)包含了添加元素的新集。代碼3.5中,你使用的是不可變集,因此+調(diào)用將產(chǎn)生一個(gè)全新集。因此盡管可變集提供的實(shí)際上是+=方法,不可變集卻不是。本例中,代碼的第二行,“jetSet += "Lear"”,實(shí)質(zhì)上是下面寫(xiě)法的簡(jiǎn)寫(xiě):
- jetSetjetSet = jetSet + "Lear"
因此在代碼3.5的第二行,你用一個(gè)包含了"Boeing","Airbus"和"Lear"的新集重新賦值了jetSet這個(gè)var。最終,代碼3.5的最后一行打印輸出了集是否包含字串"Cessna"。(正如你所料到的,輸出false。)
如果你需要不可變集,就需要使用一個(gè)引用:import,如代碼3.6所示:
- import scala.collection.mutable.Set
- val movieSet = Set("Hitch", "Poltergeist")
- movieSet += "Shrek"
- println(movieSet)
代碼 3.6 創(chuàng)建,初始化,和使用可變集
代碼3.6的第一行里引用了可變Set。就像Java那樣,引用語(yǔ)句允許你使用簡(jiǎn)單名,如Set,以替代更長(zhǎng)的,全標(biāo)識(shí)名。結(jié)果,當(dāng)你在第三行寫(xiě)Set的時(shí)候,編譯器就知道你是指scala.collection.mutable.Set。在那行里,你使用包含字串"Hitch"和"Poltergeist"的新可變集初始化了movieSet。下一行通過(guò)在集上調(diào)用+=方法向集添加了"Shrek"。正如前面提到的,+=是實(shí)際定義在可變集上的方法。如果你想的話,你可以替換掉movieSet += "Shrek"的寫(xiě)法,寫(xiě)成movieSet.+=("Shrek")。
盡管目前為止看到的通過(guò)可變和不可變的Set工廠方法制造的缺省的集實(shí)現(xiàn)很可能能夠滿足極大多數(shù)的情況,但偶爾你也或許想要個(gè)顯式的集類。幸運(yùn)的是,語(yǔ)法是相同的。只要引用你需要的類,并使用它伴生對(duì)象的工廠方法即可。例如,如果你需要一個(gè)不可變的HashSet,你可以這么做:
- import scala.collection.immutable.HashSet
- val hashSet = HashSet("Tomatoes", "Chilies")
- println(hashSet + "Coriander")
Map是Scala里另一種有用的集合類。和集一樣,Scala采用了類繼承機(jī)制提供了可變的和不可變的兩種版本的Map,你能在圖3.3里看到,Map的類繼承機(jī)制看上去和Set的很像。scala.collection包里面有一個(gè)基礎(chǔ)Map特質(zhì)和兩個(gè)子特質(zhì)Map:可變的Map在scala.collection.mutable里,不可變的在scala.collection.immutable里。
Map的實(shí)現(xiàn),如顯示在類繼承圖3.3里的HashMap,擴(kuò)展了要么可變,要么不可變特質(zhì)。你可以使用與那些用在數(shù)組,列表和集中的一樣的工廠方法去創(chuàng)造和初始化映射。例如,代碼3.7展示了可變映射的創(chuàng)造過(guò)程:
- import scala.collection.mutable.Map
- val treasureMap = Map[Int, String]()
- treasureMap += (1 -> "Go to island.")
- treasureMap += (2 -> "Find big X on ground.")
- treasureMap += (3 -> "Dig.")
- println(treasureMap(2))
代碼 3.7 創(chuàng)造,初始化,和使用可變映射
代碼3.7的第一行里,你引用了可變形式的Map。然后就定義了一個(gè)叫做treasureMap的val并使用空的包含整數(shù)鍵和字串值的可變Map初始化它。映射為空是因?yàn)槟銢](méi)有向工廠方法傳遞任何值(“Map[Int, String]()”的括號(hào)里面是空的)。 下面的三行里你使用->和+=方法把鍵/值對(duì)添加到Map里。像前面例子里演示的那樣,Scala編譯器把如1 -> "Go to island"這樣的二元操作符表達(dá)式轉(zhuǎn)換為(1).->("Go to island.")。因此,當(dāng)你輸入1 -> "Go to island.",你實(shí)際上是在值為1的Int上調(diào)用->方法,并傳入值為"Go to island."的String。這個(gè)->方法可以調(diào)用Scala程序里的任何對(duì)象,并返回一個(gè)包含鍵和值的二元元組。 然后你在把這個(gè)元組傳遞給treasureMap指向的Map的+=方法。最終,最后一行輸出打印了treasureMap中的與鍵2有關(guān)的值。如果你執(zhí)行這段代碼,將會(huì)打印:
- Find big X on ground.
如果你更喜歡不可變映射,就不用引用任何類了,因?yàn)椴豢勺冇成涫侨笔〉?,代碼3.8展示了這個(gè)例子:
- val romanNumeral = Map(
- 1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
- )
- println(romanNumeral(4))
代碼 3.8 創(chuàng)造,初始化,和使用不可變映射
由于沒(méi)有引用,當(dāng)你在代碼3.8的第一行里提及Map時(shí),你會(huì)得到缺省的映射:scala.collection.immutable.Map。傳給工廠方法入五個(gè)鍵/值元組,返回包含這些傳入的鍵/值對(duì)的不可變Map。如果你執(zhí)行代碼3.8中的代碼,將會(huì)打印輸出IV。
本文節(jié)選自《Programming in Scala》
【相關(guān)閱讀】