Scala編程實(shí)例:使用List和Tuple
本節(jié)接著上一節(jié)的內(nèi)容,繼續(xù)介紹Scala編程中一些更先進(jìn)的特征:List(數(shù)組)和Tuple(元組)。
51CTO編輯推薦:Scala編程語(yǔ)言專(zhuān)題
Scala中使用List
方法不應(yīng)該有副作用是函數(shù)風(fēng)格編程的一個(gè)很重要的理念。方法唯一的效果應(yīng)該是計(jì)算并返回值。用這種方式工作的好處就是方法之間很少糾纏在一起,因此就更加可靠和可重用。另一個(gè)好處(靜態(tài)類(lèi)型語(yǔ)言里)是傳入傳出方法的所有東西都被類(lèi)型檢查器檢查,因此邏輯錯(cuò)誤會(huì)更有可能把自己表現(xiàn)為類(lèi)型錯(cuò)誤。把這個(gè)函數(shù)式編程的哲學(xué)應(yīng)用到對(duì)象世界里意味著使對(duì)象不可變。
如你所見(jiàn),Scala數(shù)組是一個(gè)所有對(duì)象都共享相同類(lèi)型的可變序列。比方說(shuō)Array[String]僅包含String。盡管實(shí)例化之后你無(wú)法改變Array的長(zhǎng)度,它的元素值卻是可變的。因此,Array是可變的對(duì)象。
說(shuō)到共享相同類(lèi)型的不可變對(duì)象序列,Scala的List類(lèi)才是。和數(shù)組一樣,List[String]包含的僅僅是String。Scala的List,scala.List,不同于Java的java.util.List,總是不可變的(而Java的List可變)。更通常的說(shuō)法,Scala的List是設(shè)計(jì)給函數(shù)式風(fēng)格的編程用的。
創(chuàng)建一個(gè)List很簡(jiǎn)單。代碼3.3做了展示:
- val oneTwoThree = List(1, 2, 3)
代碼 3.3 創(chuàng)造和初始化列表
代碼3.3中的代碼完成了一個(gè)新的叫做oneTwoThree的val,并已經(jīng)用帶有整數(shù)元素值1,2和3的新List[Int]初始化。 因?yàn)長(zhǎng)ist是不可變的,他們表現(xiàn)得有些像Java的String:當(dāng)你在一個(gè)List上調(diào)用方法時(shí),似乎這個(gè)名字指代的List看上去被改變了,而實(shí)際上它只是用新的值創(chuàng)建了一個(gè)List并返回。比方說(shuō),List有個(gè)叫“:::”的方法實(shí)現(xiàn)疊加功能。你可以這么用:
- val oneTwo = List(1, 2)
- val threeFour = List(3, 4)
- val oneTwooneTwoThreeFour = oneTwo ::: threeFour
- println(oneTwo + " and " + threeFour + " were not mutated.")
- println("Thus, " + oneTwoThreeFour + " is a new List.")
如果你執(zhí)行這個(gè)腳本,你會(huì)看到:
- List(1, 2) and List(3, 4) were not mutated.
- Thus, List(1, 2, 3, 4) is a new List.
或許List最常用的操作符是發(fā)音為“cons”的‘::’。Cons把一個(gè)新元素組合到已有List的最前端,然后返回結(jié)果List。例如,若執(zhí)行這個(gè)腳本:
- val twoThree = list(2, 3)
- val oneTwoThree = 1 :: twoThree
- println(oneTwoThree)
你會(huì)看到:
- List(1, 2, 3)
注意
表達(dá)式“1 :: twoThree”中,::是它右操作數(shù),列表twoThree,的方法。你或許會(huì)疑惑::方法的關(guān)聯(lián)性上有什么東西搞錯(cuò)了,不過(guò)這只是一個(gè)簡(jiǎn)單的需記住的規(guī)則:如果一個(gè)方法被用作操作符標(biāo)注,如a * b,那么方法被左操作數(shù)調(diào)用,就像a.*(b)——除非方法名以冒號(hào)結(jié)尾。這種情況下,方法被右操作數(shù)調(diào)用。因此,1 :: twoThree里,::方法被twoThree調(diào)用,傳入1,像這樣:twoThree.::(1)。
5.8節(jié)中將描述更多操作符關(guān)聯(lián)性的細(xì)節(jié)。
由于定義空類(lèi)的捷徑是Nil,所以一種初始化新List的方法是把所有元素用cons操作符串起來(lái),Nil作為最后一個(gè)元素。
比方說(shuō),下面的腳本將產(chǎn)生與之前那個(gè)同樣的輸出
- “List(1, 2, 3)”:
- val oneTwoThree = 1 :: 2 :: 3 :: Nil
- println(oneTwoThree)
Scala的List包裝了很多有用的方法,表格3.1羅列了其中的一些。列表的全部實(shí)力將在第十六章釋放。
為什么列表不支持append?
類(lèi)List沒(méi)有提供append操作,因?yàn)殡S著列表變長(zhǎng)append的耗時(shí)將呈線性增長(zhǎng),而使用::做前綴則僅花費(fèi)常量時(shí)間。如果你想通過(guò)添加元素來(lái)構(gòu)造列表,你的選擇是把它們前綴進(jìn)去,當(dāng)你完成之后再調(diào)用reverse;或使用ListBuffer,一種提供append操作的可變列表,當(dāng)你完成之后調(diào)用toList。ListBuffer將在22.2節(jié)中描述。
Scala中使用Tuple
另一種有用的容器對(duì)象是元組:tuple。與列表一樣,元組也是不可變的,但與列表不同,元組可以包含不同類(lèi)型的元素。而列表應(yīng)該是List[Int]或List[String]的樣子,元組可以同時(shí)擁有Int和String。元組很有用,比方說(shuō),如果你需要在方法里返回多個(gè)對(duì)象。Java里你將經(jīng)常創(chuàng)建一個(gè)JavaBean樣子的類(lèi)去裝多個(gè)返回值,Scala里你可以簡(jiǎn)單地返回一個(gè)元組。而且這么做的確簡(jiǎn)單:實(shí)例化一個(gè)裝有一些對(duì)象的新元組,只要把這些對(duì)象放在括號(hào)里,并用逗號(hào)分隔即可。一旦你已經(jīng)實(shí)例化了一個(gè)元組,你可以用點(diǎn)號(hào),下劃線和一個(gè)基于1的元素索引訪問(wèn)它。代碼3.4展示了一個(gè)例子:
- val pair = (99, "Luftballons")
- println(pair._1)
- println(pair._2)
代碼 3.4 創(chuàng)造和使用元組
代碼3.4的第一行,你創(chuàng)建了元組,它的第一個(gè)元素是以99為值的Int,第二個(gè)是"luftballons"為值的String。Scala推斷元組類(lèi)型為T(mén)uple2[Int, String],并把它賦給變量pair。第二行,你訪問(wèn)_1字段,從而輸出第一個(gè)元素,99。第二行的這個(gè)“.”與你用來(lái)訪問(wèn)字段或調(diào)用方法的點(diǎn)沒(méi)有區(qū)別。本例中你正用來(lái)訪問(wèn)名叫_1的字段。如果執(zhí)行這個(gè)腳本,你能看到:
- 99
- Luftballons
元組的實(shí)際類(lèi)型取決于它含有的元素?cái)?shù)量和這些元素的類(lèi)型。因此,(99, "Luftballons")的類(lèi)型是Tuple2[Int, String]。('u', 'r', 'the', 1, 4, "me")是Tuple6[Char, Char, String, Int, Int, String]。
訪問(wèn)元組的元素
你或許想知道為什么你不能像訪問(wèn)List里的元素那樣訪問(wèn)元組的,就像pair(0)。那是因?yàn)長(zhǎng)ist的apply方法始終返回同樣的類(lèi)型,但是元組里的或許類(lèi)型不同。_1可以有一個(gè)結(jié)果類(lèi)型,_2是另外一個(gè),諸如此類(lèi)。這些_N數(shù)字是基于1的,而不是基于0的,因?yàn)閷?duì)于擁有靜態(tài)類(lèi)型元組的其他語(yǔ)言,如Haskell和ML,從1開(kāi)始是傳統(tǒng)的設(shè)定。
本文節(jié)選自《Programming in Scala》
【相關(guān)閱讀】