scala 學習筆記(三)

scala進一步學習

1.帶類型的參數化數組

scala可以使用new實例化對象或者實例。當你在scala裏實例化對象,可以使用值和類型把他參數化。參數化意思就是創建實例時候“設置”它。通過把加在括號裏的對象傳遞給實例的構造器的方式來使用值參數化實例。

如:scala裏實例化一個新的java.math.BigInteger並使用值"123456"參數化

val big = new java.math.BigInteger("123456")
通過方括號裏設定一個或更多類型來參數化實例。

val greetString = new Arrary[String](3)
greetString(0) = "hello"
greetString(1) = ", "
greetString(2) = "World!\n"
for (i <- 0 to 2)
   print(greetString(i))

Scala 裏的數組是通過把索引放在圓括號裏面訪問的,而不是像 Java 那樣放在方括號裏。所以數組的第零個元素是 greetStrings(0),不是 greetStrings[0]。 

當你用 val 定義一個變量,那麼這個變量就不能重新賦值,但它指向的對象卻仍可以暗自改變。所以在本例中,你不能把 greetStrings 重新賦值成不同的數組;greetStrings 將永遠指向那個它被初始化時候指向的同一個 Array[String]實例。但是你一遍遍修改那個 Array[String]的元素,因此數組本身是可變的。 

本例中的to實際上是帶一個Int參數的方法。代碼0 to 2被轉換成方法調用(0).to(2)1請注意這個語法僅在你顯示指定方法調用的接受者時才起作用。不可以寫 pringln 10,但是可以寫成“Console println 10”。 

當你在一個或多個值或變量外使用括號時,Scala 會把它轉換成對名爲 apply 的方法調用。 於是 greetStrings(i)轉換成 greetStrings.apply(i)。 

任何對某些在括號中的參數的對象的應用將都被轉換爲對 apply 方法的調用。當然前提是這個類型實際定義過 apply 方法。所以這不是一個特例,而是一個通則。 

,當對帶有括號幷包括一到若干參數的變量賦值時,編譯器將把它轉化爲對帶有括號裏參數和等號右邊的對象的 update 方法的調用。 

greetString(0) = "Hello"
轉化爲

greetStrings.update(0, "Hello")
我們可以把二中的code寫成

val greetString = new Array[String](3)
greetStrings.update(0"Hello")
greetStrings.update(1, ", ")
greetStrings.update(2, "world!\n")
for (i <- 0.to(2))
   print(greetStrings.apply(i))
接下來,Scala 提供了通常可以用在你真實代碼裏的更簡潔的方法創造和初始化數組 ,這行代碼創建了長度爲 3 的新數組,用傳入的字串"zero","one""two"初始化。編譯器推斷數組的類型是Array[String] ,因爲你把字串傳給它。 

val numNames = Array("zero","b","c")

這裏實際做的就是調用了一個叫做apply的工廠方法,從而創造並返回了新的數組。 

2.使用list

scala創建一個list

把這個函數式編程的哲學應用到對象世界裏意味着使對象不可變。 

list是不可變。

val oneTwoThree = List(1,2,3)

List有個叫“:::”的方法實現疊加功能。 

val oneTwo = List(1,2)
val threeFour = List(3,4)
val OneTwoThreeFour = oneTwo ::: threeFour
println(oneTwo + " and" + threeFour + " is a new List.")
println("Thus, " + oneTwoTherrFour + " is a new List.")
執行結果

List(1,2)and List(3,4) were not mutated.
Thus, List(1,2,3,4) is a new List

或許 List 最常用的操作符是發音爲“cons”的‘::Cons 把一個新元素組合到已有 List的最前端,然後返回結果 List。例如,若執行這個腳本: 

val twoThree = list(2,3)
val oneTwoThree = 1:: twoThree
println(oneTwoThree)
結果

List(1,2,3)

爲什麼列表不支持 append

List 沒有提供 append 操作,因爲隨着列表變長 append 的耗時將呈線性增長,而使用::做前綴則僅花費常量時間。如果你想通過添加元素來構造列表,你的選擇是把它們前綴進去,當你完成之後再調用 reverse;或使用 ListBuffer,一種提供 append 操作的可變列表,當你完成之後調用 toList。 

方法名 方法作用

List() Nil

List 

List("Cool", "tools", "rule)

創建帶有三個值"Cool","tools""rule"的新List[String] 

val thrill = "Will"::"fill"::"until"::Nil 

創建帶有三個值"Will","fill""until"的新 List[String] 

List("a", "b") ::: List("c", "d") 

疊加兩個列表(返回帶"a","b","c""d"的新List[String]

thrill(2)

返回在 thrill 列表上索引爲 2(基於 0)的元素(返回"until"

thrill.count(s => s.length == 4) 

計算長度爲4String元素個數(返回2)

thrill.drop(2)

返回去掉前 2 個元素的 thrill 列表(返回 List("until")

thrill.dropRight(2)

返回去掉後 2 個元素的 thrill 列表(返回 List("Will")

thrill.exists(s => s == "until") 

判斷是否有值爲"until"的字串元素在thrill裏(返回true

thrill.filter(s => s.length == 4) 

依次返回所有長度爲 4 的元素組成的列表(返回 List("Will","fill")

thrill.forall(s => s.endsWith("1")) 

辨別是否thrill列表裏所有元素都以"l"結尾(返回true

thrill.foreach(s => print(s)) 

thrill列表每個字串執行print語句("Willfilluntil"

thrill.foreach(print)

與前相同,不過更簡潔(同上)

thrill.head 

返回 thrill 列表的第一個元素(返回"Will")

thrill.init 

返回 thrill 列表除最後一個以外其他元素組成的列表(返回List("Will", "fill"))

thrill.isEmpty 

說明 thrill 列表是否爲空(返回 false)

thrill.last 

返回 thrill 列表的最後一個元素(返回"until")

thrill.length 

返回 thrill 列表的元素數量(返回 3

thrill.map(s => s + "y") 

返回由thrill列表裏每一個String元素都加了"y"構成的列表(返回List("Willy", "filly", "untily")

thrill.mkString(", ") 

用列表的元素創建字串(返回"will, fill, until"

thrill.remove(s => s.length == 4) 

返回去除了thrill列表中長度爲4的元素後依次排列的元素列表(返回 List("until")

thrill.reverse 

返回含有 thrill 列表的逆序元素的列表(返回 List("until","fill", "Will"))

thrill.sort((s, t) =>s.charAt(0).toLowerCase <t.charAt(0).toLowerCase) 

返回包括 thrill 列表所有元素,並且第一個字符小寫按照字母順序排列的列表(返回List("fill", "until", "Will")

thrill.tail 

返回除掉第一個元素的 thrill 列表(返回 List("fill","until")

3.Tuple的應用

與列表一樣,元組也是不可變的,但與列表不同,元組可以包含不同類型的元素。而列表應該是 List[Int]List[String]的樣子,元組可以同時擁有 Int String。元組很有用,比方說,如果你需要在方法裏返回多個對象。 

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

4.使用set和map

因爲 Scala 致力於幫助你充分利用函數式和指令式風格兩方面的好處,它的集合類型庫於是就區分了集合類的可變和不可變。 

例如,數組始終是可變的,而列表始終不可變。 

Scala 於是提供了兩個子特質,一個是可變的集,另一個是不可變的集。 

1)例:初始化,和使用不可變集 

var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
println(jetSet.contains("Cessna"))
<div class="page" title="Page 53"><div class="layoutArea"><div class="column"><p><span style="font-size: 10.000000pt; font-family: 'SimSun'">你用一個包含了</span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">"Boeing"</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">,</span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">"Airbus"</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">和</span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">"Lear"</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">的新集重新賦值了 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">jetSet </span><span style="font-size: 10.000000pt; font-family: 'SimSun'">這個 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">var</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">。最終,上面代碼</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">的最後一行打印輸出了集是否包含字串</span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">"Cessna"</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">。(正如你所料到的,輸出 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">false</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">。) </span></p></div></div></div>

2)如果你需要不可變集,就需要使用一個引用 

import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist")
movieSet += "Shrek"
println(movieSet)

它伴生對象的工廠方法即可。例如,如果你需要一個不可變的HashSet,你可以這麼做: 

import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander")


Map Scala 裏另一種有用的集合類。和集一樣,Scala 採用了類繼承機制提供了可變的和不可變的兩種版本的 Map。 

1)如果你需要一個不可變的HashSet

import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander")
PS:scala.collection 包裏面有一個基礎 Map 特質和兩個子特質 Map:可變的 Map scala.collection.mutable 裏,不可變的在 scala.collection.immutable 裏。 
2)
創造,初始化,和使用可變映射 
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))

下面的三行裏你使用->+=方法把鍵/值對添加到Map裏。像前面例子裏演示的那樣,Scala編譯器把如 1 -> "Go toisland"這樣的二元操作符表達式轉換爲(1).->("Go to island.")。因此,當你輸入 1 ->"Go to island.",你實際上是在值爲 1 Int上調用->方法,並傳入值爲"Go to island."String。這個->方法可以調用Scala程序裏的任何對象,並返回一個包含鍵和值的二元元組。然後你在把這個元組傳遞給treasureMap指向的Map+=方法。最終,最後一行輸出打印了treasureMap中的與鍵 2 有關的值。 

3)如果你更喜歡不可變映射,就不用引用任何類了,因爲不可變映射是缺省的,以下代碼展示了這個例子: 

val romanNumeral = Map(
  1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
println(romanNumeral(4))

由於沒有引用,當你在代碼的第一行裏提及 Map 時,你會得到缺省的映射:scala.collection.immutable.Map。傳給工廠方法入五個鍵/值元組,返回包含這些傳入的鍵/值對的不可變 Map。如果你執行代碼中的代碼,將會打印輸出 IV。


5.學習識別函數式風格

Scala 允許一種指令式風格編程,一種更函數式的風格。 

如果代碼包含了任何 var 變量,那它大概就是指令式的風格。如果代碼根本就沒有var——就是說僅僅包含 val——那它大概是函數式的風格。因此向函數式風格推進的一個方式,就是嘗試不用任何 var 編程。 

如下例子使用了var指令式風格:
def printArgs(args: Array[String]): Unit = {
  var i = 0
  while (i < args.length) {
    println(args(i))
    i += 1 }
}
通過去掉var變成函數式風格:
def printArgs(args: Array[String]): Unit = {
  for (arg <- args)
    println(arg)
}
或者
def printArgs(args: Array[String]): Unit = {
  args.foreach(println)
}
<div class="page" title="Page 56"><div class="layoutArea"><div class="column"><p><span style="font-size: 10.000000pt; font-family: 'SimSun'">重構後的 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">printArgs </span><span style="font-size: 10.000000pt; font-family: 'SimSun'">方法並不是</span><span style="font-size: 10.000000pt; font-family: 'FangSong_GB2312'">純</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">函數式的,因爲它有副作用</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">——</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">本例中,其副作用是打印到標準輸出流。函數有副作用的馬腳就是結果類型爲 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">Unit</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">。 </span></p></div></div></div>

爲函數式風格可以幫助你寫出更易讀懂,更不容易犯錯的代碼。 

更函數式的方式應該是定義對需打印的 arg 進行格式化的方法,但是僅返回格式化之後的字串 

def formatArgs(args: Array[String]) = args.mkString("\n")

現在纔是真正函數式風格的了:滿眼看不到副作用或者 var。 

args 包含了三個元素,"zero","one""two",formatArgs 將返回"zero\none\ntwo"。 

當然,這個函數並不像 printArgs 方法那樣實際打印輸出,但可以簡單地把它的結果傳遞給 println 來實現:

println(formatArgs(args))

你可以通過檢查結果來測試 formatArgs

val res = formatArgs(Array("zero", "one", "two"))
assert(res == "zero\none\ntwo")

Scala assert 方法檢查傳入的 Boolean 並且如果是假,拋出 AssertionError。如果傳入的 Boolean 是真,assert 只是靜靜地返回。 

PS:雖如此說,不過請牢記在心:不管是 var 還是副作用都不是天生邪惡的。Scala 不是強迫你用函數式風格編任何東西的純函數式語言。它是一種指令式/函數式混合的語言。你或許發現在某些情況下指令式風格更符合你手中的問題,在這時候你不應該對使用它猶豫不決。 

6.讀取文件信息

建立一個從文件中讀行記錄,並把行中字符個數前置到每一行,打印輸出的腳本 

import scala.io.Source

if (args.length > 0) {
   for (line <- Source.fromFile(args(0)).getLines)
   println(line.length + " " + line)
} 
else Console.err.println("Please enter filename")

調用運行:
$ scala countchars1.scala countchars1.scala

以上代碼改寫:
<div class="page" title="Page 59"><div class="layoutArea"><div class="column"><p><span style="font-size: 10.000000pt; font-family: 'SimHei'">對文件的每行記錄打印格式化的字符數量 </span></p><p><span style="font-size: 10.000000pt; font-family: 'SimHei'"></span><pre name="code" class="java">import scala.io.Source
def widthOfLength(s: String) = s.length.toString.length
if (args.length > 0) {
  val lines = Source.fromFile(args(0)).getLines.toList
  val longestLine = lines.reduceLeft(
    (a, b) => if (a.length > b.length) a else b
  )
  val maxWidth = widthOfLength(longestLine)
  for (line <- lines) {
    val numSpaces = maxWidth widthOfLength(line)
    val padding = " " * numSpaces
    print(padding + line.length +" | "+ line)
} }
else
Console.err.println("Please enter filename")


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章