1. 程式人生 > >快學Scala 學習筆記-1: (第一章到第三章)

快學Scala 學習筆記-1: (第一章到第三章)

第一章.
1.1Scala直譯器(REPL)
Scala> 8*5+2
res0: Int = 42
Scala> 0.5 * res0
res1: Double = 21.0
Scala直譯器 讀取到一個表示式,對它進行求值,將它打印出來,接著再繼續讀取下一個表示式。這個過程被稱作讀取-求值-列印-迴圈,即 REPL 。
從技術上講,scala程式並不是一個直譯器。實際上你輸入的內容被快速的編譯成位元組碼,然後JVM執行。因此,大多數程式設計師稱它做REPL.

1.2 宣告變數
val:常量
var:變數
宣告值或變數 不做初始化會報錯,因為scala會根據值推斷型別。

·語句無需分號,換行即可
·指定值、變數型別
var greet,message : String = null

1.3 常用型別
·Scala和Java不同,不區分基本資料型別和包裝類,所有的都是類。
譬如可以 1.toString() //產生字串"1" 或者
1.to(10) //產出Range(1,2,3,4,5,6,7,8,9,10)
其實,基本型別和包裝型別的轉換是Scala編譯器的工作。舉例,你建立了一個Int[] 陣列,最終在虛擬機器得到的是一個 

int[]陣列
·Scala底層用java.lang.String類來表示字串。不過,它通過StringOps類給字串追加了上百種操作。舉例說
"Hello".intersect("World")//共有部分,輸出 "lo"
這個表示式中"Hello"被隱式轉換成了一個StringOps物件,接著StringOps類的intersect方法被呼叫。
同樣,Scala還提供RichInt,RichDouble等,他們分別為Int、Double提供了很多不具備的便捷方法,譬如前面用到的 1.to
(10)
·數值型別間轉換
99.44toInt //得到99
99.toChar //得到'c'
"99.44".toDouble 

1.4算數和操作符過載
操作符 實際上是方法,a+b 實際上是a.+(b),因為:
Scala 幾乎可以使用任何符號來為方法命名,這裡+就是方法名。舉例:
BigInt類中有一個 /%方法,返回一個對偶(商和餘數)

Scala中,可以用
a 方法 b  作為 a.方法(b) 的簡寫,如
1.to(10)  簡寫為 1 to 10

Scala沒有 ++ 、--  可以用+= ,-=  

1.5呼叫函式和方法
·數學函式
import scala.math._  // _是Scala的萬用字元(java中的*)
sqrt(2) //1.4142...
pow(2,4) //16.0
min(3,Pi) //3.0

注意,使用以scala.開頭的包是,可以省去scala字首。
如import scala.math._ 簡寫為 import math._
scala.math.sqrt(2)  簡寫為 math.sqrt(2)

·沒有靜態方法
有單例物件。一個類對應一個伴生物件(companion object),其方法就跟Java中的靜態方法一樣。舉例:
BigInt類的BigInt伴生物件有一個生成指定位數的隨機素數的方法probablePrime:
BigInt.probablePrime(100,scala.util.Random)
注意:這裡的Random是一個單例的隨機數生成器物件,而該物件是在scala.util包中定義的。在 Java中為每個隨機數都構造一個新的java.util.Random物件是一個常見的錯誤。

·不帶引數的Scala方法通常不使用括號。一般來講:沒有引數且不改變當前物件的方法不帶圓括號。
"Hello".distinct    //獲取字串中不重複的字元

1.6apply方法
C++ : s[i]
Java: s.charAt(i)
Scala: s(i)
例: "Hello"(4) //'o'
背後的實現原理是一個叫做apply的方法(你可以把這當做是()操作符的過載)。
StringOps類中有這樣的方法:
def apply(n:Int):Char
也就是說  "Hello"(4) 實際上呼叫的是 "Hello".apply(4)
apply是Scala中建立物件的手法,如
BigInt("1234567890") * BigInt("1234567890")
Array(1,3,5,67,222)返回一個數組 就是呼叫Array伴生物件的apply方法。

1.7 Scaladoc
Scaladoc 擁有比Java多得多的便捷方法。有些方法使用了你還沒學到的特性。
www.scala-lang.org/api 線上瀏覽Scaladoc
www.scala-lang.org/downloads#api  下載
檢視doc一些技巧:
·數值型別看看RichXxx,字串看 StringOps
·數學函式位於 scala.math包中,而不是某一個類
·標記為 implicit 的方法對應的是 自動(隱式)轉換。
·方法引數 可以傳 函式。
def count(p:(char) => Boolean) :Int
如 s.count(_.isUpper) //清點所有大寫字母的數量

1.8練習題
Scala REPL中,
resX 變數是 val還是 var?   : val
"crazy"*3 得到什麼?  "crazycrazycrazy"
用BigInt計算2的1024次方?BigInt(2).pow(1024)
建立隨機檔案的方式之一是 生成一個隨機的BigInt,然後將它轉換成三十六進位制,輸出類似"awsgrwsger52d42"這樣的字串。查閱Scaladoc,找到在Scala中實現該邏輯的辦法?
scala.math.BigInt(scala.util.Random.nextInt).toString(36) 
// res1: String = utydx
Scala中如何獲取字串的首字元和尾字元?
"Hello"(0)   "Hello".take(1)
"Hello".reverse(0)   "Hello".takeRight(1)

take:  從字串首開始獲取字串
drop:  從字串首開始去除字串

takeRight/dropRigth: 從字串尾開始


二.控制結構和函式
Scala和其他程式語言的一個根本性差異:Java或C++中 我們把表示式(3+4)和語句(if else)看作兩樣不同的東西。在Scala中,幾乎所有構造出來的語法結構都有值。
·if表示式有值
val s = if (x>0) 1 else -1
類似於Java的三目運算子 ?:
val v = if (x>0) "1" else -1
v的型別是 Int和String 的超類 Any;
如果 else 沒有輸出值,引入Unit類,寫作()
val v=if(x>0) "1" else   等於
val v=if(x>0) "1" else ()
補: Scala中沒有switch語句,不過有一個強大的多的模式匹配機制。

注意: REPL中若想換行,用花括號 { }
REPL想貼上成塊程式碼,又不想擔心REPL的“近視”問題(只讀一行),輸入 :paste 進入貼上模式 
最後按 Ctrl+D ,這樣REPL就會整體執行。

·void型別是 Unit
·塊也有值————就是它最後一個表示式
在Scala中,賦值動作本身是沒有(返回)值的
一個以 賦值語句結束的塊 返回的值 是Unit型別的
所以,不要這樣做:
x=y=1   //x的值是 Unit

·輸入和輸出
print()
println()
帶有C風格 格式化字串的 printf函式:
printf("Hello,%s!You are %d  years old.\n","godce",42);
輸入:
readLine("提示字串") 從控制檯讀取

·Scala的for迴圈就像 Java增強for迴圈(foreach)
Scala中沒有 fro(int i=0;i<n;i++) 這樣的結構,
用while替換 或者
for(i<- 1 to n)
r=r*i
讓變數i遍歷(<-) 右邊的表示式的所有值。至於遍歷具體如何執行,取決於表示式的型別。
補:在遍歷字串或陣列時,通常需要使用從0到n-1區間。這個時候可以用until方法而不是to方法。util返回一個不包含上

限的區間
val s= "hello"
var sum=0
for(i<-0 util s.length)
sum+=s(i)
更簡便的寫法:
var sum = 0
for(ch<- "hello") sum+=ch
補:Scala沒有提供 break或continue語句來推出迴圈。如何取代?
法一.使用Boolean型控制變數
法二.使用巢狀函式--你可以從函式中return
法三.使用Breaks物件中的break方法 scala.util.control.Breaks.break

·高階for迴圈和for推導
變數<-表示式  提供多個生成器,用分號將他們隔開:
for(i<-1 to 3;j<-1 to 3) print((10*i+j)+" ")
// 11 12 13 21 22 23 31 32 33
每個生成器都可以帶一個守衛——if開頭的Boolean表示式
for(i<-1 to 3;j<-1 to 3 if i!=j) print((10*i+j)+" ")
//  12 13 21 23 31 32
可以使用任意多的定義,引入可以在迴圈中使用的變數:
for(i<-1 to 3;from = 4-i; j<-from to 3) print((10*i+j)+" ")
//  13 22 23 31 32 33
如果for迴圈的迴圈體以yield開始,則該迴圈會構造出一個集合,每次迭代生成集合中的一個值:
for(i<-1 to 10) yield i%3
//生成 Vector(1,2,0,1,2,0,1,2,0,1)
這類迴圈叫做 for推導式
for推導式 生成的集合 與它的第一個生成器是型別相容的
for(c<-"hello";i<- 0 to 1) yield (c+i).toChar
//生成   "HIeflmlmop"
for(i<- 0 to 1;c <- "hello") yield (c+i).toChar
//生成 Vector('H','e','l','l','o','I','f','m','m','p')

·函式
Scala還支援函式,不過在Java中我們只能用靜態方法來模擬。
defabs(x:Double) = if(x>=0) x else -x
函式名 引數 型別  函式體
注意! 只要函式不是遞迴,你就不需要指定返回型別(Scala編譯器會通過=符號右側的表示式的型別推斷出返回型別)
例子:
def fac(n:Int) = {
var r = 1
for(i<1 to n) r=r*i
r
}
一般不用return,不過也可以用return從函式中退出,不常見。
·避免在函式定義中使用return

·遞迴函式必須指定 返回型別
def fac(n:Int):Int = if(n<=0) 1 else n* fac(n-1)
如果沒有返回型別,Scala編譯器無法校驗 n*fac(n-1)的型別是Int
擴充套件: 某些程式語言(如ML和Haske11)能夠推斷出遞迴函式的型別,用的是Hindley-Milner演算法。不過,在面對物件的語言中這樣做並不總是行得通。如何擴充套件Hindley-Milner演算法讓他能夠處理子型別仍然是個科研命題。

·預設引數和帶名引數
def decorate(str:String,left:String="[",right:String="]") =
left+str+right
decorate("Hello") //[Hello]
decorate("Hello","<",">") //<Hello>
decorate("Hello","<") //<Hello]
decorate(left="<<<",str="Hello",right=">>>") //<<<Hello>>>

上面 帶名引數 讓函式更加可讀(尤其是有很多預設引數的函式)

·變長引數
def sum(args:Int*) = {
var result=0
for(arg <-args) result+=arg
result
}
val s = sum(1,4,9,16,25)
函式得到的是一個型別為Seq的引數。
val s = sum(1 to 5) //錯誤!傳入單個引數那麼改引數必須是單個正數,而不是一個整數區間!
val s = sum(1 to 5 :_*) //告訴編譯器 將1to5當做引數序列處理
遞迴舉例:
def recursiveSum(args:Int*):Int= {
if(args.length == 0) 0
else args.head + recursiveSum(args.tail:_*)
}

//這裡 序列的head是它的首個元素,而tail是所有其他元素的區間,所以我們用 :_* 來將它 轉換成 引數序列。

·過程
沒有返回值的函式。算作幾個語句組合起來的塊,呼叫僅僅為了執行這個過程,並不需要得到返回值,如列印五角星。
Scala中 過程沒有 = 號
def box(s:String) {  //顯示宣告無返回值 加上 :Unit
var border = "-" * s.length+"--\n"
println(border +"|"+s+"|\n"+border)
}
·注意別在函式式定義中漏掉了= 

·lazy 懶值 延遲賦值(被用到時才執行)
lazy val words = scala.io.Source.fromFile("/usr/words").mkString

·異常的工作方式 和Java或C++中基本一樣,不同的是你在catch語句中使用"模式匹配"
·Scala沒有受檢異常
在Java中,"受檢"異常在編譯期被檢查。如果你的方法可能會丟擲IOException,你必須做出宣告。這就要求程式設計師必須去想哪些異常應該在哪裡被處理掉,這是個值得稱道的目標。不幸的是,它同時也催生了怪獸般的方法簽名,比如void doSomething()throws IOException,InterruptedException,ClassNotFoundException。許多Java程式設計師很反感這個特性,最終過早捕獲這些異常,或者使用超通用的異常類。Scala的設計者們決定不支援"受檢"異常,因為他們意識到徹底的編譯期檢查並不總是好的。
例:
if(x>=0) sqrt(x) else throw new IllegalArgumentException("x should not be negative")
第一個分值型別是 Double,第二個分值型別是 Nothing。因此if/else表示式的返回型別是Double

·練習題
2.1.編寫函式:正數返回1,0返回0,負數返回-1
BigInt(10).signum
2.2.一個空的塊表示式{}的值是什麼?型別是什麼?
在 REPL中可以看出:
Scala> val t = {}
t: Unit= ()
返回值是(),型別是Unit
2.3.指出在Scala中何種情況下賦值語句x=y=1是合法的
val x = {}
2.4.針對下列Java迴圈編寫一個Scala版本:for(int i=10;i>=0;i--)System.out.println(i); 
for(i<- 0 to 10 reverse)print(i)
2.5.編寫一個過程countdown(n:Int),列印從n到0的數字
def countdown(n:Int) {
for(i<-0 to n reverse) print(i)
// 0 to n reverse foreach print
}
2.6 編寫一個for迴圈,計算字串中所有字母的Unicode程式碼的乘積。舉例來說,"Hello"中所有字串的乘積為9415087488L 
for(i <- "Hello") {
t = t*i.toLong
}
2.7 不使用for迴圈解決2.6
Scaladoc中 檢視StringOps 文件
var t:Long = 1
"Hello".foreach(t *= _.toLong)
2.8 將 2.6計算乘積的程式碼編成函式product(s:String)
def product(s:String):Long={
var t:Long = 1
for(i<-s){
t*=i.toLong
}
t
}
2.9 將2.8的函式改為遞迴
def product(s:String):Long={
if(s.length == 1) return s.charAt(0).toLong
else s.take(1).charAt(0).toLong * product(s.drop(1))
}

三. 陣列相關操作
要點:
·如長度固定用Array,不固定用ArrayBuffer
·提供初始值時不要使用new
·用()來訪問元素
·用for(elem<-arr)來遍歷元素
·用for(elem<-arr if...)...yield...來將原陣列轉型為新陣列
·scala陣列和Java陣列可以互操作;用ArrayBuffer,使用scala.collection.JavaConversions中的轉換函式

3.1 定長陣列
val nums = new Array[Int](10)
val s = Array("hello","world")
//已經提供初始值就無需new
s(0)="goodbye"
//Array("goodbye","world")
使用()而不是[]來訪問元素

3.2 變長陣列:陣列緩衝
import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]()
//或者 new ArrayBuffer[Int]
//一個空的陣列緩衝,準備存放整數
b+=1
//ArrayBuffer(1)
//用+=在尾端新增元素
b+=(1,2,4,5)
b++=Array(8,13,21)
//ArrayBuffer(1,1,2,4,5,8,13,21)
//你可以用++=操作符追加任何集合
b.trimEnd(5)
//ArrayBuffer(1,1,2)
//移除最後5個元素
b.insert(2,6,7)
//ArrayBuffer(1,1,6,7,2)
b.remove(2,2)
//ArrayBuffer(1,1,2)
val a = b.toArray
//Array(1,1,2)
val c = a.toBuffer

3.3 遍歷陣列和陣列緩衝
for(i<-0 until a.length)
// 0 until 10 實際上是一個方法呼叫: 0.until(10)
如果想要每兩個元素一跳,可以這樣寫:
0 until (a.length,2)
從陣列尾端開始:
(0 until a.length).reverse
如果迴圈體中不需要用到陣列下標:
for(elem <- a)
//這和 增強for迴圈 很相似

3.4陣列轉換
val a = Array(2,3,6,7,10)
val result = for(elem <- a) yield 2*elem
// result是Array(4,6,12,14,20)
還可以通過守衛:for中的if來實現只處理滿足條件的元素
for(elem<-a if elem % 2==0) yield 2*elem

擴充套件: 另一種做法:
a.filter(_%2 == 0).map(2 * _)
甚至
a.filter{_%2 == 0} map{2*_}
某些有著函數語言程式設計經驗的程式設計師傾向於使用filter和map而不是守衛和yield.
這不過是一種風格罷了--for迴圈所做的事完全相同。
考慮如下示例:
給定一個整數的陣列緩衝,要求移除 除了第一個負數外的所有負數。傳統的依次執行的解決方案會在遇到第一個負數時置一個標記,然後移除後續出現的負數元素:
var first = true
var n = a.length
var i = 0
while(i<n) {
if(a(i) >=0) i+=1
else {
if(first) {first = false;i+=1}
else {a.remove(i);n-=1}
}
}
不過等一下————這個方案其實並不那麼好:從陣列緩衝中移除元素並不高效,把非負數值拷貝到前端要好得多。
首先收集需要保留的下標:
var first = true
val indexes = for(i<-0 until a.length if first || a(i)>=0) yield {
if (a(i)<0) first = false; i
}
然後將元素移動到該去的位置,並截斷尾端:
for(j<-0 until indexes.length) a(j)=a(indexes(j))
a.trimEnd(a.length - indexes.length)

3.5 常用演算法
求和:
Array(1,3,4,5).sum
比較:
ArrayBuffer("Mary","had","little").max  //"little"
排序:
val b = ArrayBuffer(1,4,2,5)
val bSorted = b.sorted(_<_) // 原陣列並未被改變
val bDescending = b.sorted(_>_) // ArrayBuffer(5,4,2,1)
對自身排序:只能對陣列,不能對陣列緩衝
val a = Array(1,7.2,9)
scala.util.Sorting.quickSort(a)
// a現在是 Array(1,2,7,9)
顯示: mkString 允許你指定元素間的分隔符
a.mkString(" and ")
//該方法的另一個過載 可以指定字首字尾
b.mkString("<","-",">")
//<1-4-2-5>
它們的toString 方法:
a.toString
// 呼叫的是Java的 毫無意義的 toString
b.toString
//ArrayBuffer(1,4,2,5)
//便於除錯 重寫了toString

3.6 解讀Scaladoc
Scala文件 讀起來獲取有些困難,又學會忽略那些複雜的細節,簡化成簡單的版本。

3.7 多維陣列
和Java一樣,多維陣列是通過陣列的陣列來實現的。
Double的二維陣列型別為  Array[Array[Double]]
//通過 ofDim方法
val matrix=Array.ofDim[Double](3,4) //三行 四列
matrix(row)(column) = 43
//建立不規則陣列,每一行長度都不同
val triangle = new Array[Array[Int]](10)
for(i<- 0 util triangle.length)
triangle(i) = new Array[Int](i+1)

3.8與Java的互操作
由於Scala陣列是用Java陣列實現的,你可以在java和Scala之間來回傳遞。
如果你呼叫接收或返回 java.util.List 的Java方法,則當然可以在Scala程式碼中使用Java的ArrayList————但那樣做沒什麼意思。你完全可以引入scala.collection.JavaConversions裡的隱式轉換方法。這樣你就可以在程式碼中使用scala緩衝,在呼叫Java方法時,這些物件會被自動包裝成Java列表。
舉例:java.lang.ProcessBuilder類有一個以List<String>為引數的構造器。以下是在Scala中呼叫它的寫法:
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ArrayBuffer
val command = ArrayBuffer("ls","-al","/home/cay")
val pb = new ProcessBuilder(command)    //Scala 到 Java的轉化
Scala緩衝被包裝成了一個實現了java.util.List介面的Java類的物件
反過來講,當Java方法返回java.util.List時,我們可以讓它自動轉換成一個Buffer
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ArrayBuffer
val cmd : Buffer[String] = pb.command()  // Java 到 Scala的轉換

3.9練習題
3.9.1. 編寫一段程式碼,將a設定為一個n個隨機整數的陣列,要求隨機數介於0和n之間。
val n = 100
val rand = scala.util.Random
val a = new Array[Int](n)
for(i<- 0 util n) a(i)=rand.nextInt(n)
println(a.mkString(" and "))

3.9.2. 編寫一個迴圈,將整數陣列中相鄰的元素置換。
例如,Array(1,2,3,4,5)置換後變為Array(2,1,4,3,5)
var a = Array(1,2,3,4,5)
for(i<-0 util (a.length-1,2) ) {
a[i] = a[i] ^ a[i+1]
a[i+1] = a[i] ^ a[i+1]
a[i] = a[i] ^ a[i+1]
}
3.9.3  重複第2題,交換過的陣列生成新的值,用for/yield
var a = Array(1,2,3,4,5)
val b = for(i<-0 util (a.length-1,2)) 
yield 
Array(a(i+1),a(i))
println(b.mkString(" and "))

3.9.4. 給定一個整數陣列,產出一個新的陣列,包含元素組中的所有正值,以原有順序排列,之後的元素是所有零或負值,以原有順序排序。
val a = Array(1,-1,-2,4,0,33,23,0,-34,-9)
val b = for(i<-0 util a.length if a(i)>0) yield a(i)
val c = for(i<-0 util a.length if a(i)<1) yield a(i)
val buffer = b.toBuffer
buffer++=c
println(buffer.mkStirng(" and ")

3.9.5 如何計算Array[Double]的平均值
val h = Array(1.2,23.24,22,112.3)
val avg = h.sum/h.length

3.9.6 如何重新組織Array[Int]的元素將它們以反序排列?對於ArrayBuffer[Int]你又會怎麼做呢?
// Array[Int]
val arr = Array(1,2,3,4,5)
for(i<-0 util arr.length/2) {
arr(i) = arr(i) ^ arr(arr.length-1-i)
arr(arr.length-1-i) = arr(i) ^ arr(arr.length-1-i)
arr(i) = arr(i) ^ arr(arr.length-1-i)
}
println(arr.mkString(" "))
//ArrayBuffer
val arr = ArrayBuffer(1,2,3,4,5)
val arrReverse = arr.reverse
println(arr.mkStirng(" "))

3.9.7編寫程式碼,去掉陣列中的所有值,去掉重複項。(檢視Scaladoc)
val arr = Array(1,2,3,4,5,3,4,5,6,7)
println(arr.distinct.mkString(" "))

3.9.9建立一個由java.util.TimeZone.getAvailableIDs返回的時區集合,判斷條件是它們在美洲。去掉”America/”字首並排序.
val timeZone = java.util.TimeZone.getAvailableIDs
val americaTimeZone = timeZone.filter(_.take(8)=="America/")
val sortedAmericaTimeZone = americaTimeZone.map(_.drop(8)).sorted

3.9.10引入java.awt.datatransfer._並構建一個型別為SystemFlavorMap型別的物件
val flavors = SystemFlavorMap.getDefaultFlavorMap().asInstanceOf[SystemFlavorMap]
然後以DataFlavor.imageFlavor為引數呼叫getNativesForFlavor方法,以Scala緩衝儲存返回值。(為什麼用這樣一個晦澀難懂的類?因為在Java標準中很難找得到使用java.util.List的程式碼。)
import java.awt.datatransfer._
val flavors = SystemFlavorMap.getDefaultFlavorMap().asInstanceOf[SystemFlavorMap]
flavors.getNativesForFlavor(DataFlavor.imageFlavor).toArray.toBuffer
println(xx.mkString("  "))