1. 程式人生 > >scala 學習筆記(三)

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")