柚子快報(bào)邀請(qǐng)碼778899分享:快學(xué)Scala
柚子快報(bào)邀請(qǐng)碼778899分享:快學(xué)Scala
第一章
1.1 Scala解釋器
Scala解釋器讀取到一個(gè)表達(dá)式,對(duì)它進(jìn)行求值,將它打印出來(lái),接著再繼續(xù)讀下一個(gè)表達(dá)式。這個(gè)過(guò)程稱作讀取-求值-打印-循環(huán),即REPL。
實(shí)際發(fā)生的是,你輸入的內(nèi)容被快速地編譯成字節(jié)碼,然后這段字節(jié)碼交由java虛擬機(jī)執(zhí)行。
val定義常量
var定義變量
不需要類型。不夠在必要的時(shí)候,可以指定類型:
val greeting: String = null
val greeting: Any = "Hello"
變量或函數(shù)的類型總是寫在變量或函數(shù)名稱的后面。
無(wú)分號(hào),僅當(dāng)同一行代碼中存在多條語(yǔ)句時(shí)才需要分號(hào)隔開(kāi)。
val xmax,ymax = 100 // 將xmax和ymax都設(shè)置為100 注意和c++不同
七種數(shù)據(jù)類型:Byte , Char , Short , Int , Long ,Float , Double , Boolean
這些類型都是類,跟Java的不同之處。
Scala并不刻意區(qū)分基本類型和引用類型。你可以對(duì)數(shù)字執(zhí)行方法。
1.toString() // "1"
1.to(10) // Range(1-10)
Scala底層使用java.lang.String類表示自負(fù)床。不過(guò),它通過(guò)StringOps類給字符串追加了上百種操作。
前者會(huì)被隱式轉(zhuǎn)換為后者,再調(diào)用方法。
RichInt,RichDouble,RichChar分別為其原有類型提供了便捷方法。
BigInt , BigDecimal 用于任意大?。ǖ懈F)的數(shù)字。
Scala使用方法而不是強(qiáng)制類型轉(zhuǎn)換,來(lái)做數(shù)值類型之間轉(zhuǎn)換。
99.44.toInt
99.toChar
xx.toString
通常來(lái)說(shuō),可以用
a 方法 b 等價(jià)于 a.方法(b)
沒(méi)有++ 和 – 運(yùn)算符,需要使用 +=1或者-=1
能夠?qū)Σ僮鞣M(jìn)行重載 !@$&*這樣的運(yùn)算符
1.5 調(diào)用函數(shù)和方法
import scala.math._ 在Scala中,_字符是通配符,類似于Java中的*
使用scala前綴的包,可以省去scala前綴。
沒(méi)有靜態(tài)方法。單例對(duì)象。一個(gè)類有一個(gè)伴生對(duì)象,其方法就跟Java中的靜態(tài)方法一樣。例如:
BigInt.probablePrime(100,scala.util.Random)
Random是一個(gè)單例的隨機(jī)數(shù)生成器對(duì)象,而該對(duì)象是在scala.util包中定義的
不帶參數(shù)的Scala方法通常不使用圓括號(hào)。
"Hello"(4) // 將產(chǎn)出'o'
可以當(dāng)作()的重載形式,原理是apply方法。
def apply(n: Int): Char <=> "Hello".apply(4)
BigInt("1234567789")
使用伴生對(duì)象的apply方法是Scala構(gòu)造對(duì)象的常用方法。
1.7 Scaladoc
www.scala-lang.org/api
對(duì)應(yīng)的類(C)或伴生對(duì)象(O)
第二章
所有語(yǔ)法 都可有值
2.1 條件表達(dá)式
if (x>0) 1 else -1
val s = if (x>0) 1 else -1
例如:
if (x>0) "positive" else -1
上述表達(dá)式的類型是兩個(gè)分支類型的公共超類型。 Any
又如:
if (x>0) 1
可能沒(méi)有輸出,但又需要有某種值。引入U(xiǎn)nit類,寫作() , 等價(jià)于
if (x>0) 1 else ()
可以當(dāng)作void。
沒(méi)有switch語(yǔ)句
解釋器“近視”只看一行
if (x>0) { 1
} else if (x == 0) 0 else -1
在解釋器復(fù)制粘貼代碼
:paste
粘貼
CTRL+D
2.2 語(yǔ)句終止
if (n>0) {
r = r*n
n -= 1
}
這種方式較好
2.3 塊表達(dá)式和賦值
在Scala中,{}塊包含一系列表達(dá)式,其結(jié)果也是個(gè)表達(dá)式。塊中最后一個(gè)表達(dá)式的值就是塊的值。
val distance = { val dx = x-x0;val dy=y-y0; sqrt(dx*dx+dy*dy)}
賦值語(yǔ)句的返回類型是Unit類型。
2.4 輸入和輸出
println
printf()
readLine(“提示”)
2.5 循環(huán)
while (n>0) {
}
for (i <- 表達(dá)式)
表達(dá)式:to與util方法。
for ( i <- 0 util s.length)
沒(méi)有for循環(huán)對(duì)應(yīng)的
沒(méi)有break continue
可以使用
布爾類型控制變量 使用嵌套函數(shù)——你可以從函數(shù)當(dāng)中return 使用 Breaks對(duì)象中的break方法 import scala.util.control.Breaks._
breakable {
for (...) {
if (...) break; //退出breakable塊
}
}
2.6 高級(jí)for循環(huán)和for推導(dǎo)式
for ( i <- 1 to 3 ; j <- 1 to 3 if i != j)
for循環(huán)的循環(huán)體以 yield開(kāi)始,則該循環(huán)會(huì)構(gòu)造出一個(gè)集合,每次迭代生成集合中的一個(gè)值:
for (i <- 1 to 10) yield i%3 //生成 Vector(1,2,0,1,2,0,1,2,0,1)
這類循環(huán)叫做for推導(dǎo)式。
for推導(dǎo)式生成的集合與它第一個(gè)生成器是類型兼容的。
for (c<- "Hello"; i <-0to1) yield (c+i).toChar // 等價(jià)于兩個(gè) for循環(huán) 遍歷字母 每個(gè)字母自己和+1 "HIexxx"
for (i <- 0 to 1; c <- "Hello") yield (c+i).toChar
for { i <- 1 to 3
from = 4 - i
j <- from to 3}
2.7 函數(shù)
def abs(x: Double) = if (x>=0) x else -x
必須給出所有參數(shù)的類型。如果函數(shù)不是遞歸的,那么就不需要給出返回的類型。
scala可以通過(guò)= 右側(cè)的表達(dá)式類型推斷。
如果函數(shù)體需要多個(gè)表達(dá)式完成,可以用代碼塊。塊中最后一個(gè)表達(dá)式的值就是函數(shù)的返回值。
def fac(n: Int) = {
var r =1
for (i <- 1 to n) r= r* i
r
}
匿名函數(shù)中,如果使用return并不返回值給調(diào)用者。它跳出到包含它的帶名函數(shù)中。
可以把return當(dāng)作是函數(shù)版的break語(yǔ)句
對(duì)于遞歸函數(shù),必須指定返回類型
def fac(n: Int): Int = if(n<=0) 1 else n * fac(n-1)
2.8 默認(rèn)參數(shù)和帶名參數(shù)
def decorate(str: String, left: String = "[" , right: String = "]") =
left + str + right
混用未命名的參數(shù):未命名的參數(shù)在前面,帶名的放在后面。帶名參數(shù)還是很有用的。
decorate("Hello",right = "]<<<<")
2.9 變長(zhǎng)參數(shù)
def sum(args: Int*) = {
var result = 0
for (arg <- args) result += arg
result
}
任意多的參數(shù)。
val s = sum(1,4,9,16,25)
函數(shù)得到的是一個(gè)類型為Seq的參數(shù)。
如果sum函數(shù)被調(diào)用時(shí)傳入的是單個(gè)參數(shù),那么該參數(shù)必須是單個(gè)整數(shù) 。 : _*當(dāng)作序列處理!
val s = sum(1 to 5: _*) // 將 1 to 5 當(dāng)作參數(shù)序列處理
def recursiveSum(args: Int*): Int = {
if ( args.length == 0) 0
else args.head + recursiveSum(args.tail: _*)
}
在這里,序列的head是它的首個(gè)元素,而tail是所有其他元素的序列,這又是一個(gè)Seq,我們用:_*來(lái)將它轉(zhuǎn)換成參數(shù)序列。
2.10 過(guò)程
Scala對(duì)于不返回值的函數(shù)有特殊的表示法。如果函數(shù)體包含在花括號(hào)當(dāng)中但沒(méi)有前面的=號(hào),那么返回類型就是Unit。這樣的函數(shù)被稱為過(guò)程。過(guò)程不返回值,調(diào)用只是為了它的副作用。
def box(s: String) {
var border = "-" * s.length + "--\n"
printLn(border + "|" + s "|\n" + border)
}
顯式聲明Unit返回類型:其實(shí)全部都寫等號(hào)好了,這樣的話更明朗。
def box(s: String): Unit = {
...
}
2.11 懶值
lazy val words = scala.io.Source.fromFile("/usr/share/dict/words").mkString
當(dāng)val被聲明為lazy時(shí),它的初始化將被推遲,直到我們首次對(duì)它取值。
懶值對(duì)于開(kāi)銷較大的初始化語(yǔ)句而言十分有用。它還以應(yīng)對(duì)其他初始化問(wèn)題,那比如循環(huán)依賴。
懶數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)。
可以把懶值當(dāng)作是介于val和def的中間狀態(tài)
val words = // 在words被定義時(shí)即被取值
lazy val words = // 在words被首次使用時(shí)取值
def words = // 在每一次words被使用時(shí)取值
懶值并不是沒(méi)有額外開(kāi)銷。我們每次訪問(wèn)懶值,都會(huì)有一個(gè)方法被調(diào)用,而這個(gè)方法將會(huì)以線程安全的方式檢查該值是否已被初始化。
2.12 異常
throw new IllegalArgumentException("x should not be negative")
拋出異常。當(dāng)前運(yùn)算被終止,運(yùn)行時(shí)系統(tǒng)查找可以接受該異常的異常處理器。
對(duì)象必須是java.lang.Throwable的子類。不過(guò),與java不同的是,scala沒(méi)有受檢異?!悴恍枰暶髡f(shuō)函數(shù)或方法可能會(huì)拋出某種異常。
throw表達(dá)式有特殊的類型Nothing。如果一個(gè)分支的類型是Nothing,那么ifelse表達(dá)式的類型就是另一個(gè)分支的類型。
模式匹配方式
try {
process()
} catch {
case _:
case ex:
}
更通用的異常應(yīng)該放在具體異常后
不許喲啊使用捕獲的異常對(duì)象,可以使用_來(lái)代替變量名
try/finally語(yǔ)句讓你可以釋放資源,不論有沒(méi)有異常發(fā)生。例如:
var in = new URL
try {
xx
} finally {
}
finally語(yǔ)句不論process函數(shù)是否拋出異常都會(huì)執(zhí)行。
try/catch是處理異常,而try/finally語(yǔ)句在異常沒(méi)有被處理時(shí)執(zhí)行某種動(dòng)作(通常是清理工作)。可以結(jié)合起來(lái)
try {...} catch {...} finally {...}
第三章 數(shù)組
長(zhǎng)度固定Array , 長(zhǎng)度可能有變化ArrayBuffer 提供初始值時(shí)不要使用new 用()來(lái)訪問(wèn)元素 用for(elem <- arr)來(lái)遍歷元素 用for(elem <- arr if …) … yield … 來(lái)將原數(shù)組轉(zhuǎn)型為新數(shù)組 Scala數(shù)組和Java數(shù)組可以互操作;用ArrayBuffer,使用scala.collection.JavaConversions中的轉(zhuǎn)換函數(shù)
3.1 定長(zhǎng)數(shù)組
val nums = new Array[Int](10) // 10個(gè)整數(shù)的數(shù)組,所有元素初始化為0
val a = new Array[String](10) // 10個(gè)元素的字符串?dāng)?shù)組,所有元素初始化為null
val s = Array("Hello","World") // 長(zhǎng)度為2的Array[String]
s(0) = "Goodbye" // 使用()訪問(wèn)元素
3.2 變長(zhǎng)數(shù)組:數(shù)組緩沖
import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]()
val c = new ArrayBuffer[Int]
b += 1 // +=尾部添加元素
b += (1,2,3,5)
b ++= Array(8,13,21) // ++=操作符追加任何集合
b.trimEnd(5) // 移除最后5個(gè)元素
b.insert(2,6) // 在下標(biāo) 2 前加入元素 6
b.insert(2,7,8,9) // 在下標(biāo) 2 前 加入任意多元素
b.remove(2) // 移除下標(biāo)為 2 的元素
b.remove(2,3) // 連續(xù)移除下標(biāo)為 2 的元素 3次
如果需要構(gòu)建一個(gè)Array,但一開(kāi)始不知道需要裝多少個(gè)元素。在這種情況下,先構(gòu)造一個(gè)數(shù)組緩沖,然后調(diào)用
b.toArray
反過(guò)來(lái)
b.toBuffer
3.3 遍歷數(shù)組和數(shù)組緩沖
數(shù)組和向量語(yǔ)法相同。
0 until a.length
0 until (a.length,2) // 兩跳
(0 until a.length).reverse
for (elem <- a)
3.4 數(shù)組轉(zhuǎn)換
轉(zhuǎn)換不會(huì)修改原始數(shù)組,而是產(chǎn)生一個(gè)全新的數(shù)組。
val a = Array(2,3,5,7,11)
val result = for (elem <- a) yield 2 * elem
val result = for (elem <- a if elem % 2 == 0) yield 2 * elem
var first = true
val indexes = for( i<- 0 until a.length if first || a(i) >= 0) yield {
if (a(i) < 0) first = true ; i
}
3.5 常用算法
Array(1,7,2,9).sum // ArrayBuffer同樣適用
sum 必須是數(shù)值類型:要么整型,要么是浮點(diǎn)數(shù)或者BigInteger/BigDecimal
min和max輸出數(shù)組或者數(shù)組緩沖中最小和最大的元素
sorted方法排序,但不會(huì)修改原始版本
val bSorted = b.sorted(_ < _) //b沒(méi)有被改變
你還可以提供一個(gè)比較函數(shù),不過(guò)你需要用sortWith方法
你可以直接對(duì)一個(gè)數(shù)組排序,但不能對(duì)數(shù)組緩沖排序:
scala.util.Sorting.quickSort(a)
對(duì)于min,max,quickSort方法,元素類型必須支持比較操作
如果要顯示數(shù)組或者數(shù)組緩沖的內(nèi)容,可以用mkString方法,它允許你指定元素之間的分隔符。
重載版本可以指定前綴和后綴。
a.mkString(" and ")
a.mkString("<",",",">")
a.toString // Array 無(wú)意義
b.toString // "ArrayBuffer(1,7,2,9)" 報(bào)告了類型,便于調(diào)試
3.6 解讀Scaladoc
有一些文檔里的解釋。挺好的。!??!
3.7 多維數(shù)組
可以使用ofDim方法:
val matrix = Array.ofDim[Double](3,4) // 三行,四列
matrix(row)(column) = 42 // 要訪問(wèn)其中的元素,使用兩對(duì)圓括號(hào)
可以創(chuàng)建不規(guī)則數(shù)組,每一行的長(zhǎng)度不同
val r = new Array[Array[Int]](10)
for ( i <- 0 util r.length)
r(i) = new Array[Int](i + 1)
3.8 與Java的互操作
第四章 映射和元組
映射是鍵值對(duì)偶的集合。元組——n個(gè)對(duì)象的聚集,并不一定要相同的類型。對(duì)偶不過(guò)是一個(gè)n=2的元組。
4.1 構(gòu)造Map
val scores = Map("Alice" -> 10,"Bob" -> 3,"Cindy" -> 8)
構(gòu)造出一個(gè)不可變的Map[String,Int]
想要一個(gè)可變的映射可以用:
val scores = scala.collection.mutable.Map("Alice" -> 10)
如果想從一個(gè)空的映射開(kāi)始,你需要選定一個(gè)映射實(shí)現(xiàn)并給出類型參數(shù):
val scores = new scala.collection.mutable.HashMap[String,Int]
也可以這樣創(chuàng)建
val scores = Map(("Alice",10),("Bob",3),("Cindy",8))
4.2 獲取映射中的值
可以使用()表示法來(lái)查找某個(gè)鍵對(duì)應(yīng)的值
val bobScore = scores("Bob")
如果映射并不包含請(qǐng)求中使用的鍵,則會(huì)拋出異常。
要檢查映射中是否有某個(gè)指定的鍵,可以使用contains方法:
val bobScore = if (scores.contains("Bob")) scores("Bob") else 0
快捷寫法:
val bobScore = scores.getOrElse("Bob",0)
scores.get(鍵) 返回一個(gè)Option對(duì)象,要么是Some(鍵對(duì)應(yīng)的值),要么是None
4.3 更新映射中的值
scores("Bob") = 10
scores("Fred") = 7 // 新增
scores += ("Bob" -> 10, "Fred" -> 7) // 新增多個(gè)
scores -= "Alice" //移除
val newScores = scores + ("Bob" -> 10, "Fred" -> 7) //更新過(guò)的新映射
要從不可變的移除,也可以使用:
scores = scores - "Alice"
不停地創(chuàng)建新映射效率不低,原因是老的和新的映射共享大部分結(jié)構(gòu)。
4.4 迭代映射
遍歷映射中所有的鍵值對(duì)偶:
for ((k,v)<- 映射) 處理k和v
scores.keySet // 類似Set("Bob","Cindy","Fred")
for( v <- scores.values) printfln(v) // 打印值
反轉(zhuǎn)一個(gè)鍵和值:
for((k,v)<-映射) yield (v,k)
4.5 已排序映射
操作映射時(shí),你需要選定一個(gè)實(shí)現(xiàn),一個(gè)哈希表或平衡樹(shù)。
默認(rèn)情況下,是哈希表。
val scores = scala.collection.immutable.SortedMap("Alice"->10)
Scala沒(méi)有可變的樹(shù)形映射
如果需要可變的樹(shù)形映射,可以使用java的TreeMap
如果要按插入順序訪問(wèn)所有鍵,使用LinkedHashMap。
val months = scala.collection.mutable.LinkedHashMap("January"->1 , "February"->2)
4.6 與Java的互操作
由TreeMap到scala的Map
import scala.collection.JavaConversions.mapAsScalaMap
val scores: scala.collection.mutable.Map[String,Int] =
new java.util.TreeMap[String,Int]
從scala映射傳遞給預(yù)期的java映射的方法,提供相反的隱式轉(zhuǎn)換:
import scala.collection.JavaConversions.mapAsJavaMap
import java.awt.font.TextAttribute._ //引入下面的映射會(huì)用到的鍵
val attrs = Map(FAMILY -> "Serif",SIZE -> 12)
val font = new java.awt.Font(attrs) //該方法預(yù)期一個(gè)Java映射
4.7 元組
元組是不同類型的值的聚集。
元組的值是通過(guò)將單個(gè)的值包含在圓括號(hào)中構(gòu)成的。例如:
(1,3.14,"Fred") 類型為:
Tuple3[Int,Double,java.lang.String]
類型定義也可以寫為:
(Int,Double,java.lang.String)
操作方法:
val t = (1,3.14,"Fred")
可以使用 _1 , _2 , _3 訪問(wèn)其組元
val second = t._2
和數(shù)組或字符串中的位置不同,元組的各組元從1開(kāi)始,而不是0
._2 等價(jià)于 空格_2
通常,使用模式匹配來(lái)獲取元組的組元,例如:
val (first,second,third) = t
val (first,second,_) =t // 不需要的位置上使用_
元組可以用于函數(shù)返回不止一個(gè)值的情況。舉例來(lái)說(shuō),StringOps的partition方法返回的是一堆字符串,分別包含了滿足某個(gè)條件和不滿足該條件的字符:
"New York".partition(_.isUpper) // 輸出對(duì)偶(“NY”,“ew ork”)
4.8 拉鏈操作
使用元組的原因之一是把多個(gè)值綁在一起,以便他們能夠被一起處理,這通??梢杂脄ip方法來(lái)完成。
val symbols = Array("<","-",">")
val counts = Array(2,10,2)
val pairs = symbols.zip(counts)
Array(("<",2),("-",10),(">",2))
for((s,n) <- pairs) Console.print(s * n) // 會(huì)打印 <<---------->>
第5章 類
TIPS:
類中必須初始化字段 方法默認(rèn)是公有的,私有的自己加private 調(diào)用無(wú)參方法可以不寫(),對(duì)于setter使用(),對(duì)于getter不用() Scala對(duì)于字段都有相應(yīng)的getter和setter方法。這兩個(gè)方法是否公有,與聲明一致。這是默認(rèn)的。在任何時(shí)候也可以自己修改 對(duì)于只讀的字段,設(shè)為val即可
var foo : 自動(dòng)生成setter與getterval foo:自動(dòng)生成getter由你來(lái)定義foo和foo_=方法由你來(lái)定義foo方法 private[this] var value = 0 更嚴(yán)格的訪問(wèn)限制。 輔助Constructor: def this(name: String) : Unit = {
this() //調(diào)用主構(gòu)造器
this.name = name
}
一個(gè)類如果沒(méi)有顯式定義主構(gòu)造器,則自動(dòng)擁有一個(gè)無(wú)參的主構(gòu)造器即可。 主構(gòu)造器直接在類名之后:主構(gòu)造器會(huì)執(zhí)行類定義中的所有語(yǔ)句。私有的字段,公有的getter與setter方法。 class Person(val name: String, val age: Int){
...//主構(gòu)造器內(nèi)容
}
主構(gòu)造器的參數(shù)可以任意:例如 private var age: Int。name: String, age: Int。如果不帶val或var的話。 class Person(name : String, age : Int) {
def description = name + " is " + age + " years old"
}
如果不帶val或var的參數(shù)至少被一個(gè)方法所使用,它將被升格為字段。類似的字段等同于private[this] val字段的效果 否則,該參數(shù)將不被保存為字段。它僅僅是一個(gè)可以被主構(gòu)造器中的代碼訪問(wèn)的普通參數(shù)。 如果不喜歡主構(gòu)造器,則使用輔助構(gòu)造器,同時(shí)調(diào)用this就可以了! TUDO 嵌套類。
5.1 簡(jiǎn)單類和無(wú)參方法
class Counter {
private var value = 0 // 你必須初始化字段
def increment() { value += 1} // 方法默認(rèn)是公有的
def current() = value
}
默認(rèn)public,并不聲明。
構(gòu)造對(duì)象-》使用方法
val myCounter = new Counter // 或 new Counter()
myCounter.increment()
println(myCounter.current)
5.2 帶getter和setter的屬性
Scala對(duì)每個(gè)字段都提供一個(gè)get和set方法。
私有字段的get和set私有
公有字段的get和set公有
class Person {
var age = 0
}
println(fred.age) //將調(diào)用方法 fred.age()
fred.age = 21 //將調(diào)用fred.age=(21)
在任何時(shí)候你都可以自己重新定義get和set方法。例如:
class Person {
private var privateAge = 0
def age = privateAge
def age_=(newValue: Int) {
if(newValue > privateAge) privateAge = newValue;
}
}
5.3 只帶get的屬性
val time = new java.util.Date
5.4 對(duì)象私有字段
類似C++,但允許更加嚴(yán)格的訪問(wèn)限制:
private[this] var value = 0 //類似于 object.value這樣的訪問(wèn)將不被允許
5.5 Bean屬性
將Scala字段標(biāo)注為@BeanProperty,這樣的方法會(huì)自動(dòng)生成。例如:
import scala.reflect.BeanProperty
class Person {
@BeanProperty var name: String = _
}
會(huì)生成:
name: Stringname_=(newValue: String): UnitgetName():StringsetName(newValue: String): Unit
private[類名] val/var name 生成的方法依賴于具體實(shí)現(xiàn),將訪問(wèn)權(quán)賦予外部類。并不經(jīng)常用到
5.6 輔助構(gòu)造器
主構(gòu)造函數(shù),輔助構(gòu)造函數(shù)
輔助構(gòu)造器的名稱為 this每一個(gè)輔助構(gòu)造器都必須以一個(gè)對(duì)先前已定義的其他輔助構(gòu)造器或主構(gòu)造器的調(diào)用開(kāi)始。
class Person {
private var name = ""
private var age = 0
def this(name: String) {
this()
this.name = name
}
def this(name: String, age: Int) {
this(name)
this.age = age
}
}
如果一個(gè)類沒(méi)有顯式定義主構(gòu)造器則自動(dòng)擁有一個(gè)無(wú)參的主構(gòu)造器。
5.7 主構(gòu)造器
主構(gòu)造器不以this方法定義,而是與類定義交織在一起
主構(gòu)造器的參數(shù)直接放置在類名之后
class Person(val name: String, val age: Int) {
// (上面括號(hào)內(nèi))中的內(nèi)容就是主構(gòu)造器的參數(shù)
}
name和age都會(huì)成為Person的字段。
主構(gòu)造器會(huì)執(zhí)行類定義中的所有語(yǔ)句。例如在以下類中:
class Person(val name: String=“”, val age: Int = 0) {
println("Just constructed another person")
def description = name + " is " + age + " years old"
}
每當(dāng)有對(duì)象被構(gòu)造出來(lái),上述代碼就會(huì)被執(zhí)行。
構(gòu)造參數(shù)也可以是普通的方法參數(shù),不帶val或var。這樣的參數(shù)如何處理取決于他們?cè)陬愔腥绾伪皇褂谩?/p>
如果不帶val或var的參數(shù)至少被一個(gè)方法所使用,它將被升格為字段。例如: class Person(name: String, age: Int) {
def description = name + " is " + age + " years old"
}
上述代碼聲明并初始化了不可比那字段name和age,而這兩個(gè)字段都是對(duì)象私有的。 等價(jià)于 private[this] val字段的效果 否則,該參數(shù)將不被保存為字段。它僅僅是一個(gè)可以被主構(gòu)造器中的代碼訪問(wèn)的普通參數(shù)。
NOTE:
如果想讓主構(gòu)造器變成私有的,可以像這樣放置private關(guān)鍵字:
class Person private(val id: Int) {...}
5.8 嵌套類
可以在任何語(yǔ)法結(jié)構(gòu)中內(nèi)嵌任何語(yǔ)法結(jié)構(gòu)。在函數(shù)中定義函數(shù),在類中定義類。
內(nèi)部類在不同對(duì)象中是不同的
import scala.collection.mutable.ArrayBuffer
class Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Member]
}
private val members = new ArrayBuffer[Member]
def join(name:String) = {
val m = new Memeber(name)
members += m
m
}
}
也就是說(shuō) 相同名的內(nèi)部類不能相互賦值
利用伴生對(duì)象解決
object Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Member]
}
}
class Network {
private val members = new ArrayBuffer[Network.Member]
}
或者可以使用類型投影Netwokr#Member,其含義是“任何Network的Member”
class Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Network#Member]
}
}
如果你只想在某些地方,而不是所有地方,利用這個(gè)細(xì)粒度的“每個(gè)對(duì)象有自己的內(nèi)部類”的特性,可以考慮使用類型投影。
在內(nèi)嵌類中,可以使用 外部類.this的方式來(lái)訪問(wèn)外部類的this引用,也可以建立一個(gè)指向該引用的別名:
class Network(val name: String) { outer =>
class Member(val name: String) {
...
def description = name + " inside " + outer.name
}
}
outer變量指向Network.this。對(duì)這個(gè)變量,你可以用任何合法的名稱。
第六章 對(duì)象
總結(jié):TIPS
為了彌補(bǔ)沒(méi)有靜態(tài)方法或靜態(tài)字段而生。 類與它的伴生對(duì)象可以相互訪問(wèn)私有特性。必須存在一個(gè)源文件中。 擴(kuò)展類或特質(zhì)的對(duì)象 Object(參數(shù)1,…,參數(shù)N):調(diào)用apply方法。在Object中定義: object Account {
def apply(initialBalance: Double) =
new Account(newUniqueNumber(),initialBalance)
}
寫main函數(shù)有兩種方法: object Hello {
def main(args: Array[String]) {
println("Hello,World!")
}
}
//(由于 DelayedInit,編譯器對(duì)該特質(zhì)有特殊的處理)P74
object Hello extends App { // 擴(kuò)展App特質(zhì),直接寫具體的main函數(shù)即可。內(nèi)置了args參數(shù)。
println("Hello,World")
}
枚舉不是關(guān)鍵字,是一個(gè)特質(zhì):Enumeration類。定義一個(gè)擴(kuò)展Enumeration類的對(duì)象并以Value方法調(diào)用初始化枚舉中的所有可選值。例如: object TrafficLightColor extends Enumeration {
val Red,Yellow,Green = Value
}
每次調(diào)用Value方法都返回內(nèi)部類的新實(shí)例,該內(nèi)部類也叫做Value。
val Red = Value(0,"Strop")
val Yellow = Value(10) // 名稱為"Yellow"
val Green = Value("Go")
如果不指定,則ID在將前一個(gè)枚舉值基礎(chǔ)上加1,從0開(kāi)始。缺省名稱為字段名。 調(diào)用方法:TrafficLightColor.Red、TrafficLightColor.Yellow、TrafficLightColor.Green。 也可以直接import TrafficLgihtColor._ 注意枚舉的類型是:TrafficLightColor.Value
6.1 單例對(duì)象
Scala沒(méi)有靜態(tài) 方法和對(duì)象,可以使用object來(lái)實(shí)現(xiàn)。
object Accounts {
private var lastNumber = 0
def newUniqueNumber = { lastNumber += 1 ; lastNumber }
}
Account.newUniqueNumber()可以調(diào)用。
對(duì)象的構(gòu)造器在第一次被使用的時(shí)候調(diào)用。
6.2 伴生對(duì)象
class Account {
val id = Account.newUniqueNumber()
private var balance = 0.0
def deposit(amount: Double) { balance += amount; }
}
object Account { // 伴生對(duì)象
private var lastNumber = 0
private def newUniqueNumber() = { lastNumber += 1; lastNumber}
}
類和它的伴生對(duì)象可以相互訪問(wèn)私有特性。他們必須存在于同一個(gè)源文件中。
6.3 擴(kuò)展類或特質(zhì)的對(duì)象
一個(gè)object可以擴(kuò)展類以及一個(gè)或多個(gè)特質(zhì),其結(jié)果是一個(gè)擴(kuò)展了指定類以及特質(zhì)的類的對(duì)象,同時(shí)擁有在對(duì)象定義中給出的所有特性。
一個(gè)有用的例子是給出可被共享的缺省對(duì)象。舉例,可撤銷動(dòng)作的類:
abstract class UndoableAction(val description: String) {
def undo(): Unit
def redo(): Unit
}
object DoNothingAction extends UndoableAction("Do nothing") {
override def undo() {}
override def redo() {}
}
6.4 apply方法
apply方法返回的是伴生類的對(duì)象。
Array("Mary","had","a")
Array(100)與 new Array(100) 的區(qū)別。
前者調(diào)用apply方法,后者調(diào)用構(gòu)造函數(shù) this(100) 結(jié)果是Array[Nothing],包含了100個(gè)null元素
定義apply方法的示例:
class Account private (val id: Int, initialBalance: Double) {
private var balance = initialBalance
}
object Account {
def apply(initialBalance: Double) =
new Account(newUniqueNumber(),initialBalance)
}
這樣就可以利用如下代碼構(gòu)造:
val acct = Account(1000.0)
6.5 應(yīng)用程序?qū)ο?/p>
每個(gè)Scala程序都必須從一個(gè)對(duì)象的main方法開(kāi)始,這個(gè)方法的類型為Array[String] => Unit
object Hello {
def main(args: Array[String]) {
println("Hello,World!")
}
}
除了每次都提供自己的main方法外,你也可以擴(kuò)展App特質(zhì),然后講代碼放入構(gòu)造函數(shù)中
object Hello extends App {
println("Hello,World!")
}
object Hello extends App {
if (args.length > 0)
println("Hello, " + args(0))
else
println("Hello, World!")
}
如果在調(diào)用該應(yīng)用程序時(shí)設(shè)置了scala.time選項(xiàng)的話,程序退出時(shí)會(huì)顯示逝去的時(shí)間。
scalac Hello.scala
scala -Dscala.time Hello Fred
Hello, Fred
[total 4ms]
App特質(zhì)擴(kuò)展自另一個(gè)特質(zhì)DelayedInit,編譯器對(duì)該特質(zhì)有特殊處理。所有帶有該特質(zhì)的類,其初始化方法都會(huì)被挪到delayedInit方法中。App特質(zhì)的main方法捕獲到命令行參數(shù),調(diào)用delayedInit方法,并且還可以根據(jù)要求打印出逝去的時(shí)間。
6.6 枚舉
沒(méi)有枚舉類型。標(biāo)準(zhǔn)庫(kù)提供了Enumeration助手類
定義一個(gè)擴(kuò)展Enumeration類的對(duì)象并以Value方法調(diào)用初始化枚舉中的所有可選值。例如:
object T extends Enumration {
val Red,Yello,Green = Value
}
或者,你也可以向Value方法傳入ID,名稱,或兩個(gè)參數(shù)都傳:
val Red = Value(0,"Stop")
val Yellow = Value(10) // 名稱為“Yellow”
val Green = Value("Go") // ID = 11
如果不指定,則ID在講前一個(gè)枚舉值的基礎(chǔ)上加1,從0開(kāi)始。缺省第二個(gè)字段名稱 就是為 字段名
直接引入枚舉值:
import T._
枚舉的類型是T.Value
有人推薦增加一個(gè)類型別名:
object TrafficLightColor extends Enumeration {
type TrafficLightColor = Value
val Red,Yello,Green = Value
}
枚舉的類型變成TrafficLightColor.TrafficLightColor,但僅當(dāng)你使用import語(yǔ)句時(shí),這樣做才顯得有意義。
import TrafficLightColor._
def doWhat(color: TrafficLightColor) = {
if (color == Red) "stop"
else if (color == Yellow) "hurry up"
else "go"
}
枚舉類型具有id方法,返回枚舉值的ID,名稱通過(guò)toString方法返回
對(duì)TrafficLightColor.values的調(diào)用輸出所有枚舉值的集:
for ( c<- TrafficLightColor.values) println(c.id+": "+ c)
最后,可以通過(guò)枚舉的ID或名稱來(lái)進(jìn)行查找定位,以下兩段代碼都輸出TrafficLightColor.Red對(duì)象:
TrafficLightColor(0)
TrafficLigthColor.withName("Red")
第7章 包和引入
7.1 包
package com{
package horstmann {
package impatient {
class Employee
...
}
}
}
Employee.scala
與對(duì)象或類的定義不同,同一個(gè)包可以定義在多個(gè)文件當(dāng)中。在Manager.scala中
package com {
package horstmann {
package impatient {
class Manager
...
}
}
}
源文件目錄與包無(wú)關(guān)
7.2 作用域規(guī)則
Scala的包和其他作用域一樣的支持嵌套。你可以訪問(wèn)上層作用域中的名稱。例如:
package com {
package horstmann {
object Utils {
def ...
}
package impatient {
class Employee {
...
def giveRaise(rate: scala.Double) {
salary += Utils.percentOf(salary,rate)
}
}
}
}
}
這里有個(gè)陷阱,就是說(shuō) 如果定義了同名的包,不能直接引用
解決的方法之一,使用絕對(duì)包名,以_root_開(kāi)始,例如:
val subordinates = new _root_.scala.collection.mutable.ArrayBuffer[Employee]
7.3 串聯(lián)式包語(yǔ)句
package com.horstmann.impatient {
// com 和 com.horstmann的成員在這里不可見(jiàn)
package people {
class Person
...
}
}
這樣的包語(yǔ)句限定了可見(jiàn)的成員?,F(xiàn)在com.horstmann.collection包不再能夠以collection訪問(wèn)到了。
7.4 文件頂部標(biāo)記法
你可以在文件頂部使用 package語(yǔ)句,不帶花括號(hào)。例如:
package com.horstmann.impatient
package people
class Person
...
package com.horstmann.impatient {
package people {
class Person
...
//直到文件末尾
}
}
7.5 包對(duì)象
包可以包含類、對(duì)象、特質(zhì),但不能包含函數(shù)或變量的定義。由于JAVA虛擬機(jī)的局限。包對(duì)象解決
每個(gè)包都可以有一個(gè)包對(duì)象。你需要在父包中定義它,且名稱與子包一樣。例如:
package com.horstmann.impatient
package object people {
val defaultName = "John"
}
package people {
class Person {
var name = defaultName // 從包對(duì)象拿到的常量
}
}
defaultName不需要加限定詞,因?yàn)樗挥谕粋€(gè)包內(nèi)。在其他地方,可以用com.horstmann.impatient.people.defaultName訪問(wèn)到。
在幕后,包對(duì)象被編譯成帶有靜態(tài)方法和字段的JVM類,名為package.class,位于相應(yīng)的包下。
就是com.horstmann.impatient.people.package,其中有一個(gè)靜態(tài)字段defaultName。package就是類名
可以把它放在,這樣任何人想要對(duì)包增加函數(shù)或變量的話,都可以很容易找到對(duì)應(yīng)的包對(duì)象
com/horstmann/impatient/people/package.scala
7.6 包可見(jiàn)性
沒(méi)有被聲明為public、private、protected的類成員在包含該類的包中可見(jiàn)。在Scala中,你可以通過(guò)修飾符達(dá)到同樣的效果。以下方法在它自己的包中可見(jiàn):
package com.horstmann.impatient.people
class Person {
private[people] def description = "A person with name" + name
...
}
可以將可見(jiàn)度延展到上層包:
private[impatient] def description = "A person with name" + name
7.7 引入
引入可以便于縮寫。
引入某個(gè)包的全部成員:
import java.awt._
在scala中 *是合法的標(biāo)識(shí)符。
import java.awt.Color._
val c1 = RED // Color.RED
val c2 = decode("#ff0000") // Color.decode
引入語(yǔ)句把它也帶進(jìn)了作用域
7.8 任何地方都可以聲明引入
import語(yǔ)句可以出現(xiàn)在任何定法,并不僅限于文件頂部。import語(yǔ)句的效果一直延伸到包含該語(yǔ)句的塊末尾。
class Manager {
import scala.collection.mutable._
val subordinates = new ArrayBuffer[Employee]
}
7.9 重命名和隱藏方法
如果你想要引入包中的幾個(gè)成員,可以像這樣使用選取器(selector):
import java.awt.{Color,Font}
選取器允許你重命名宣導(dǎo)的成員:
import java.util.{HashMap -> JavaHashMap}
import scala.collection.mutable._
選取器HashMap => _將隱藏某個(gè)成員而不是重命名它。這僅在你需要引入其他成員時(shí)有用:
import java.util.{HashMap => _, _}
import scala.collection.mutable._
現(xiàn)在,HashMap無(wú)二義指向。
7.10 隱式引入
每個(gè)Scala程序都隱式地以如下代碼開(kāi)始:
import java.lang._
import scala._
import Predef._
java.lang總是被引入。scala包也會(huì)被引入,不過(guò)方式有點(diǎn)特殊。不像所有其他引入,這個(gè)引入被允許可以覆蓋之前的引入。舉例來(lái)說(shuō),scala.StringBuilder會(huì)覆蓋java.lang.StringBuilder而不是沖突。
Predef對(duì)象的引入。他包含了相當(dāng)多有用的函數(shù)。
由于scala包默認(rèn)被引入,對(duì)于那些以scala開(kāi)頭的包,不用寫這個(gè)前綴。
collection.mutable.HashMap
第8章 繼承
8.1 擴(kuò)展類
extends final
class Employee extends Person {
var salary = 0.0
...
}
8.2 重寫方法
重寫一個(gè)非抽象方法必須使用override修飾符,例如:
public class Person {
override def toString = getClass.getName + "[name=" + name +"]"
}
在Scala中調(diào)用超類的方法和Java完全一樣,使用super關(guān)鍵字:
public class Employee extends Person {
...
override def toString = super.toString + "[salary=" + salary + "]"
}
8.3 類型檢查和轉(zhuǎn)換
要測(cè)試某個(gè)對(duì)象是否屬于某個(gè)給定的類,可以用isInstanceOf方法。如果測(cè)試成功,你就可以用asInstanceOf方法將引用轉(zhuǎn)換為子類的引用:
if(p.isInstanceOf[Employee]) {
val s = p.asInstanceOf[Employee] // s的類型為Employee
}
如果p指向的是Employee類及其子類對(duì)象,isInstanceOf[Employee]會(huì)返回成功
如果p是Null,則返回false。
如果p不是一個(gè)Employee,那么拋出異常
如果想測(cè)試p指向的是一個(gè)Employee對(duì)象,但又不是其子類的話,可以用:
if ( p.getClass ==classOf[Employee])
classOf方法定義在scala.Predef對(duì)象中,會(huì)被自動(dòng)引入
模式匹配比類型轉(zhuǎn)換更好:
p match {
case s: Emolyee => ... //將s作為Employee處理
case _ => //p不是Employee
}
8.4 受保護(hù)字段和方法
可以將字段或方法聲明為protected。這樣的成員可以被任何子類訪問(wèn),但不能從其他位置看到。
protected的成員對(duì)于類所屬的包而言,是不可見(jiàn)的。
scala還提供了一個(gè)protected[this]的變體,將訪問(wèn)權(quán)限定在當(dāng)前的對(duì)象。
8.5 超類的構(gòu)造
輔助構(gòu)造器永遠(yuǎn)都不可能直接調(diào)用超類的構(gòu)造器
子類的輔助構(gòu)造器最終都會(huì)調(diào)用主構(gòu)造器。只有主構(gòu)造器可以調(diào)用超類的構(gòu)造器
class Employee(name: String,age: Int,val salary: Double) extends Person(name,age)
Scala類可以擴(kuò)展Java類。這種情況下,它的主構(gòu)造器必須調(diào)用Java超類的某一個(gè)構(gòu)造方法。
8.6 重寫字段
子類可以覆蓋基類的 成員字段
class Person(val name: String) {
override def toString = getClass.getNAME + "[name=" + name + "]"
}
class SecretAgent(codename: String) extends Person(codename) {
override val name = "secret"
override val toString = "secret"
}
更常見(jiàn)的例子是用val重寫抽象的def,就像 :
abstract class Person {
def id: Int
}
class Student(override val id: Int) extends Person // 學(xué)生id通過(guò)構(gòu)造器輸入
NOTE:
def 只能重寫另一個(gè)defval只能重寫另一個(gè) val 或不帶參數(shù)的 defvar 只能重寫另一個(gè)抽象的 var
8.7 匿名子類
可以通過(guò)包含帶有定義或重寫的代碼塊的方式創(chuàng)建一個(gè)匿名的子類,比如:
val alien = new Person("Fred") {
def greeting = "Greetings. Earthling!"
}
會(huì)創(chuàng)建一個(gè)結(jié)構(gòu)類型的對(duì)象(參見(jiàn)18章)。
該類型標(biāo)記為Person{def greeting: String}。你可以用這個(gè)類型作為參數(shù)類型的定義:
def meet(p: Person{def greeting: String}) {
println(p.name + "says: " + p.greeting)
}
8.8 抽象類
abstract關(guān)鍵字來(lái)標(biāo)記不能被實(shí)例化的類,通常這是因?yàn)樗哪硞€(gè)或某幾個(gè)方法沒(méi)有完整定義。例如:
abstract class Person(val name:String) {
def id: Int //沒(méi)有方法體,這是一個(gè)抽象方法
}
在子類中重寫從超類的抽象方法時(shí),你不需要使用override關(guān)鍵字。
class Employee(name: String) extends Person(name) {
def id = name.hashCode // 不需要override關(guān)鍵字
}
8.9 抽象字段
抽象字段就一個(gè)沒(méi)有初始值的字段。例如:
abstract class Person(val name: String) {
val id: Int // 沒(méi)有初始化,帶有抽象的get方法的抽象字段
var name: String // 另一個(gè)抽象字段,帶有抽象的get和set方法
}
該類為id和name定義了抽象的get方法,為name字段定義了抽象的set方法。
具體的子類必須提供具體的字段,例如:
class Employee(val id: Int) extends Person { //子類有具體的id樹(shù)形
var name = “” //和具體的name屬性
}
和方法一樣,在子類中重寫超累中的抽象字段時(shí),不需要override關(guān)鍵字。
匿名類型定制抽象字段:
val fred = new Person {
val id = 1729
var name = "Fred"
}
8.10 構(gòu)造順序和提前定義
子類重寫val并且在 超類的構(gòu)造器中使用該值的話,而 superclass 的構(gòu)造器 先于 子類的構(gòu)造器運(yùn)行。過(guò)程:
Ant的構(gòu)造器在做它自己的構(gòu)造之前,調(diào)用 Creature的構(gòu)造器Creature的構(gòu)造器將它的range字段設(shè)為10Creature的構(gòu)造器為了初始化env數(shù)組,調(diào)用range()取值器該方法被重寫以輸出(還未初始化的)Ant類的range字段值range方法返回0。(這是對(duì)象被分配空間時(shí)所有整形字段的初始值)。env被設(shè)為長(zhǎng)度為0 的數(shù)組Ant構(gòu)造器繼續(xù)執(zhí)行,將其range字段設(shè)為2
雖然range字段看上去可能是10或者2 ,但env被射程了償付為0 的數(shù)組。這里的教訓(xùn)是你在構(gòu)造器內(nèi)不應(yīng)該依賴val的值。
解決方法:
將val聲明為 final 。 這樣很安全但不靈活在超類中將 val 聲明為 lazy(參見(jiàn)第二章) 。 這樣安全但不高效在子類中使用提前定義語(yǔ)法
提前定義語(yǔ)法讓你可以在超類的構(gòu)造器執(zhí)行之前初始化子類的val字段。語(yǔ)法:
class Ant extends {
override val range = 2
} with Creature
提前定義的等號(hào)右側(cè)只能引用之前已有的提前定義,而不能使用類中的其他字段
8.11 Scala繼承層次
基本類型以及 Unit類型,都擴(kuò)展自 AnyVal。
所有其他的類都是AnyRef的子類,AnyRef相當(dāng)于 JAVA中的Object類的同義詞。
AnyVal 和 AnyRef 都擴(kuò)展自Any類,而Any類是整個(gè)繼承層級(jí)的根節(jié)點(diǎn)。
Any類定義了isInstanceOf, asInstanceOf方法,以及用于相等性判斷和哈希碼的方法。
AnyVal并沒(méi)有追加任何方法。它只是所有值類型的一個(gè)標(biāo)記。
AnyRef類追加了來(lái)自O(shè)bject類的監(jiān)視方法wait和notify/notifyAll。同時(shí)提供了一個(gè)帶函數(shù)參數(shù)的方法synchronized。這個(gè)方法等同于java中的synchronize塊。例如:
account.synchronized {account.balance += amount }
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-owqhwI5E-1662286997133)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210717204243446.png)]
建議:不適用wait、notify和synchronized,除非有充分的理由不使用更高層級(jí)的并發(fā)結(jié)構(gòu)
所有的Scala類都實(shí)現(xiàn)了 ScalaObject這個(gè)標(biāo)記接口,這個(gè)接口沒(méi)有定義任何方法。
在繼承層級(jí)的另一端是Nothing和Null類型。
Null類型的唯一實(shí)例時(shí)null值。你可以將null復(fù)制給任何的 Ref ,但不能復(fù)制給 Val類型的變量。舉例就是,Int類型不能復(fù)制Null
Nothing類型沒(méi)有實(shí)例。它對(duì)于泛型結(jié)構(gòu)更常用。空列表Nil類型時(shí)List[Nothing],它時(shí)List[T]的子類,T可以時(shí)任何類。
在Scala中,void由Unit類型表示,該類型只有一個(gè)值,那就是()。注意Unit并不是任何其他類型的超類型。但是,編譯器依然允許任何值被替換為()。
8.12 對(duì)象相等性
在Scala中,AnyRef的eq方法檢查兩個(gè)引用是否指向同一個(gè)對(duì)象。AnyRef的equals方法調(diào)用eq。當(dāng)你實(shí)現(xiàn)類的時(shí)候,應(yīng)該考慮重寫equals方法,以提供一個(gè)自然的、與你的實(shí)際情況相稱的相等性判斷。
舉例來(lái)說(shuō),如果你定義class Item(val description: String,val price: Double),你認(rèn)為相同描述和價(jià)格時(shí)候這兩個(gè)是相等的。equals的定義可以是:
final override def equals(other: Any) = {
val that = other.asInstanceOf[Item]
if (that == null) false
else description == that.description && price == that.price
}
注意:請(qǐng)確保定義的equals方法參數(shù)類型為 Any!
當(dāng)你定義equals時(shí),記得同時(shí)也定義hashCode。在計(jì)算哈希碼時(shí),只應(yīng)該使用那些你用來(lái)做相等性判斷的字段。
final override def hashCode = 13 * description.hashCode + 17 * price.hashCode
通常不直接調(diào)用equals,只需要用 == 操作符就好。對(duì)于引用類型,它會(huì)在做完必要的null檢查后調(diào)用equals方法。
第9章 文件和正則表達(dá)式
9.1 讀取行
讀取文件所有行:
import scala.io.Source
val source = Source.fromFile("myfile.txt","UTF-8")
val lineIterator = source.getLines // 結(jié)果是個(gè)迭代器
for ( l <- lineIterator) // 逐條處理這些行
val lines = source.getLines.toArray //或者對(duì)迭代器以用toArray或toBuffer方法
val contents = source.mkString //整個(gè)文件讀取為一個(gè)自負(fù)床
使用完Source對(duì)象,記得調(diào)用close
9.2 讀取字符
直接把Source對(duì)象當(dāng)作迭代器,因?yàn)镾ource類擴(kuò)展自Iterator[Char]:
for ( c<- source) //處理c
查看某個(gè)字符但又不處理掉它,調(diào)用source對(duì)象的buffered方法。這樣可以用 head方法查看下一個(gè)字符,但同時(shí)并不把它當(dāng)作是已處理的字符。
val source = Source.fromFile("myfile.txt","UTF-8")
val iter = source.buffered
while (iter.hasNext) {
if (iter.head 是符合預(yù)期的)
處理 iter.next
else
...
}
source.close()
或者,如果文件不大,可以讀取成一個(gè)字符串處理
9.3 讀取詞法單元和數(shù)字
快而臟的方式讀取源文件中所有以空格隔開(kāi)的詞法單元:
val tokens = source.mkString.split("\\S+")
將字符串轉(zhuǎn)換為數(shù)字,可以用toInt或toDouble方法。舉例:
val numbers = for( w <- tokens) yield w.toDouble
val numbers = tokens.map(_.toDouble)
從控制臺(tái)讀取數(shù)字:
val age = readInt() // readDouble readLong
假定輸入僅由單個(gè)數(shù)字,且前后都沒(méi)有空格。否則會(huì)報(bào)NumberFormatException
9.4 從URL或其他源讀取
Source對(duì)象由讀取非文件源的方法:
val source1 = Sourc.fromURL("http://horstmann.com","UTF-8")
val source2 = Source.fromString("Hello World")
val source3 = Source.stdin
9.5 讀取二進(jìn)制文件
Scala沒(méi)有提供讀取二進(jìn)制文件的方法。你需要使用Java類庫(kù)。以下是如何將文件讀取成字節(jié)數(shù)組:
val file = new File(filename)
val in = new FileInputStream(file)
val bytes = new Array[Byte](file.length.toInt)
in.read(bytes)
in.close()
9.6 寫入文本文件
Scala沒(méi)有內(nèi)建的對(duì)寫入文件的支持。要寫入文本文件,可以使用java.io.PrintWriter,例如:
val out = new PrintWriter("numbers.txt")
for (i <- 1 to 100) out.println(i)
out.close()
所有的邏輯都像我們預(yù)期的那樣,除了printf方法除外。當(dāng)你傳遞數(shù)字給printf時(shí),編譯器會(huì)抱怨說(shuō)你需要將它轉(zhuǎn)換長(zhǎng)AnyRef:
out.printf("6d %10.2f",quantity.asInstanceOf[AnyRef],price.asInstanceOf[AnyRef])
為避免這個(gè)麻煩,可以使用string類的format方法:
out.print("%6d".format(quantity))
9.7 訪問(wèn)目錄
沒(méi)有正式的訪問(wèn)某個(gè)目錄的所有文件,或者遞歸地遍歷所有目錄的類。
遍歷某個(gè)目錄下的所有子目錄的函數(shù):
import java.io.File
def subdirs(dir: File): Iterator[File] = {
val children = dir.listFiles.filter(_.isDirectory)
children.toInterator ++ children.toIterator.flatMap(subdirs _)
}
利用這個(gè)函數(shù),你可以像這樣訪問(wèn)所有子目錄:
for (d <- subdirs(dir)) 處理d
或者,java 7,可以使用 java.nio.file.Files類的walkFileTree方法 TUDO
9.8 序列化
聲明一個(gè)可被序列化的類。
@SerialVersionUID(42L) class Person extends Serializable
Serializable特質(zhì)定義在scala包,因此不需要顯示引入
如果你可以接受缺省的ID,也可以略去@注解
9.9 進(jìn)程控制
scala.sys.process包提供了用于shell程序交互的工具。你可以用scala編寫shell腳本,利用Scal提供的所有能力。
import sys.process._
"ls -al .." !
這樣的結(jié)果是ls -al .. 命令被執(zhí)行,顯示上層目錄的所有文件。執(zhí)行結(jié)果被打印到標(biāo)準(zhǔn)輸出。
sys.process包包含了一個(gè)從字符串到ProcessBuilder對(duì)象的隱式轉(zhuǎn)換。!操作符執(zhí)行的就是這個(gè)ProcessBuilder對(duì)象。
!操作符返回的結(jié)果是被執(zhí)行程序的返回值:程序成功執(zhí)行的話就是0,否則就是顯示錯(cuò)誤的非0值。
如果你使用!!而不是!的話,輸出會(huì)以字符串的形式返回:
val result = "ls -al .." !!
你還可以將一個(gè)程序的輸出以管道形式作為輸入傳送到另一個(gè)程序,用#|操作符:
"ls -al .." #| "grep sec" !
輸出重定向到文件,使用#>操作符:
"ls -al .." #> new File("output.txt") !
要追加到文件末尾而不是覆蓋,使用#>>:
"ls -al .." #>> new File("output.txt") !
要把某個(gè)文件作為輸入,使用#<:
"grep sec" #< new File("output.txt") !
從URL重定向輸入:
"grep Scala" #< new URL("http://horstmann.com/index.html") !
你可以將進(jìn)程結(jié)合一起使用,比如p #&& q(如果p成功,執(zhí)行p),以及p #|| q(如果p不成功,執(zhí)行q)。不過(guò)Scala可比shell的流轉(zhuǎn)控制強(qiáng)太多了,可以直接使用scala的流轉(zhuǎn)控制。
如果你需要再不同的目錄下運(yùn)行進(jìn)程,或者使用不同的環(huán)境變量,用Process對(duì)象的apply方法來(lái)構(gòu)造ProcessBuilder,給出命令和起使目錄,以及一串(名稱,值)對(duì)偶來(lái)設(shè)置環(huán)境變量:
val p =Process(cmd, new File(dirName), ("LANG","en_US"))
然后調(diào)用:
"echo 42" #| p !
9.10 正則表達(dá)式
scala.util.matching.Regex類實(shí)現(xiàn),構(gòu)造一個(gè)Regex對(duì)象,用String類的r方法即可:
val numPattern = "[0-9]+".r
如果正則表達(dá)式包含反斜杠或引號(hào)的話,那么最好使用“原始”字符串語(yǔ)法"""..."""
val wsnumwsPattern = """\s+[0-9]+\s+""".r
findAllIn方法返回遍歷所有匹配項(xiàng)的迭代器。你可以在for循環(huán)中使用它:
for (matchingString <- numPattern.findAllIn("99 bottles, 98 bottles"))
或者將迭代器轉(zhuǎn)換成數(shù)組:
val matches = numPattern.findAllIn("99 bottles").toArray
要找到字符串中的首個(gè)匹配項(xiàng),可以使用findFirstIn。你得到的結(jié)果是一個(gè)Option[String]
val m1 = wsnumwsPattern.findFirstIn("99 bottles")
要檢查是否某個(gè)字符串的開(kāi)始部分能匹配,可用 findPrefixOf :
numPattern.findPrefixOf("99 bottles") // Some(99)
替換首個(gè)匹配項(xiàng),或全部匹配項(xiàng):
numPattern.replaceFirstIn("99 bottles","XX")
numPattern,replaceAllIn("99 88","XX") //"XX XX"
9.11 正則表達(dá)式組
分組可以讓我們方便地獲取正則表達(dá)式的子表達(dá)式。在你想要提取的子表達(dá)式兩側(cè)加上圓括號(hào),例如:
val numitemPattern = "([0-9]+) ([a-z]+)".r
要匹配組,可以把正則表達(dá)式對(duì)象當(dāng)作"提取器"(參見(jiàn)第14章)使用,就像這樣:
val numitemPattern(num,item) = "99 bottles" // 將num設(shè)為“99”,item設(shè)為“bottles”
如果你想要從多個(gè)匹配項(xiàng)中提取分組內(nèi)容,可以像這樣使用for語(yǔ)句:
for(numitermPattern(num,item) <- numitemPattern.findAllIn("99 bottles, 98 bottles"))
處理num和item
第10章 特質(zhì)
一個(gè)類擴(kuò)展自一個(gè)或多個(gè)特質(zhì),以便使用這些特質(zhì)提供的服務(wù)。特質(zhì)可能會(huì)要求使用它的類支持某個(gè)特定的特性。
特質(zhì)可以既有抽象方法也有具體方法 重寫特質(zhì)的抽象方法不需要給出override關(guān)鍵字 多特質(zhì):class ConsoleLogger extends Logger with Coneable with Serializable。多with 類中直接使用特質(zhì),直接調(diào)用函數(shù)即可 帶有特質(zhì)的對(duì)象。可以混入一個(gè)特質(zhì)。新寫一個(gè)特質(zhì)繼承所混入的特質(zhì)。在創(chuàng)建對(duì)象的時(shí)候。可以使用val acct = new SavingsAccount with ConsoleLogger 可以混入多個(gè)特質(zhì),按序的。最先執(zhí)行最右側(cè)的特質(zhì)。 在特質(zhì)中重寫抽象方法: abstract override def log(msg: String) { //需要加上abstract override
super.log(new java.util.Date() + " " + msg)
}
特質(zhì)中也可以有字段。如果有初始值,那么就是具體的,如果沒(méi)有初始值,那么就是具體的。字段是直接加到子類之中的。 特質(zhì)中未被初始化的字段在具體的子類中必須被重寫。規(guī)定了必須要什么。 初始化特質(zhì)中的字段:使用懶值。 混入超類的特質(zhì)。在混入該特質(zhì)的時(shí)候,子類的超類也就變成了特質(zhì)原本的超類。如果還想讓子類繼承某個(gè)超類,那么要求這個(gè)超類是特質(zhì)超類的子類
10.1 為什么沒(méi)有多重繼承
特質(zhì)可以同時(shí)擁有抽象方法和具體方法,而類可以實(shí)現(xiàn)多個(gè)特質(zhì)。存在沖突。
10.2 當(dāng)做接口使用的特質(zhì)
trait Logger {
def log(msg: String) //抽象方法
}
注意,不需要聲明為abstract。特質(zhì)中未被實(shí)現(xiàn)的方法默認(rèn)就是抽象的。
子類給出實(shí)現(xiàn):
class ConsoleLogger extends Logger { // 用extends
def log(msg: String) {println(msg)} //不需要寫override
}
如果你需要的特質(zhì)不止一個(gè),可以用with關(guān)鍵字來(lái)添加額外的特質(zhì):
class ConsoleLogger extends Logger with Cloneable with Serializable
所有Java接口都可以作為Scala特質(zhì)使用
Scala類只能有一個(gè)超類,但可以有任意數(shù)量的特質(zhì)。
10.3 帶有具體實(shí)現(xiàn)的特質(zhì)
不需要一定是抽象的。
trait ConsoleLogger {
def log(msg: String) {println(msg)}
}
使用這個(gè)特質(zhì)的示例:
class SavingsAccount extends Account with ConsoleLogger {
def withdraw(amoun: Double) {
if(amount > balance) log("INsufficient funds")
else balance -= amount
}
}
注意:讓特質(zhì)擁有具體行為存在一個(gè)弊端。當(dāng)特質(zhì)改變時(shí),所有混入了該特質(zhì)的類都必須重新編譯。
10.4 帶有特質(zhì)的對(duì)象
在構(gòu)造單個(gè)對(duì)象時(shí),你可以為它添加特質(zhì)。作為示例,用標(biāo)準(zhǔn)Scala庫(kù)中的Logged特質(zhì):
trait Logged {
def log(msg: String) {}
}
class SavingsAccount extends Account with Logged {
def withdraw(amount: Double) {
if(amount > balance) log("Insufficient funds")
else ...
}
...
}
現(xiàn)在,什么都不會(huì)被記錄到日志,看上去毫無(wú)意義。但是你可以在構(gòu)造具體對(duì)象的時(shí)候”混入“一個(gè)更好的 日志記錄器的實(shí)現(xiàn)。
trait ConsoleLogger extends Logged {
override def log(msg: String) {println(msg)}
}
你可以在構(gòu)造對(duì)象的時(shí)候加入這個(gè)特質(zhì):
val acct = new SavingsAccount with ConsoleLogger
當(dāng)我們?cè)赼cct對(duì)象上嗲用log方法時(shí),ConsoleLogger特質(zhì)的log方法就會(huì)被執(zhí)行。
當(dāng)然了,另一個(gè)對(duì)象可以加入不同的而特質(zhì):
val acct2 = new SavingsAccount with FileLogger
10.5 疊加在一起的特質(zhì)
你可以為類或?qū)ο筇砑佣鄠€(gè)互相調(diào)用的特質(zhì),從最后一個(gè)開(kāi)始。這對(duì)于需要分階段加工處理某個(gè)值的場(chǎng)景很有用。
給所有日志消息添加時(shí)間戳:
trait TimestampLogger extends Logged {
override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
截?cái)噙^(guò)于冗長(zhǎng)的日志消息:
trait ShortLogger extends Logged {
val maxLength = 15
override def log(msg: String) {
super.log(
if (msg.length <= maxLength) msg else msg.substring(0,maxLength-3) + "...")
}
}
疊加在一起的順序:從最后一個(gè)開(kāi)始第一個(gè)執(zhí)行,依次往上:
val acct1 = new SavingsAccount with ConsoleLogger with TimestampLogger with ShortLogger
val acct2 = new SavingsAccount wiht CinsoleLogger with ShortLogger with TimestampLogger
// acct1 => Sun Feb 06 17:45:45 ICT 2011 Insufficient...
// acct2 => Sun Feb 06 1...
如果你需要控制具體是哪一個(gè)特質(zhì)的方法被調(diào)用,則可以在方括號(hào)里給出名稱:
super[ConsoleLogger].log(...)
這里給出的類型必須是直接超類型;你無(wú)法使用繼承層級(jí)中更遠(yuǎn)的特質(zhì)或類。
10.6 在特質(zhì)中重寫抽象方法
trait Logger {
def log(msg: String)
}
trait TimestampLogger extends Logger {
override def log(msg: String) {
super.log(new java.util.Date() + " " + msg) // super.log沒(méi)有定義 錯(cuò)誤
}
}
編譯器將super.log調(diào)用標(biāo)記為錯(cuò)誤。
根據(jù)正常的繼承規(guī)則,這個(gè)調(diào)用永遠(yuǎn)都是錯(cuò)誤的,Logger.log沒(méi)有實(shí)現(xiàn)。但實(shí)際上,就像你在前一節(jié)看到的,我們沒(méi)法知道哪個(gè)log方法最終被調(diào)用——這取決于特質(zhì)被混入的順序。
abstract override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
Scala認(rèn)為TimestampLogger依舊是抽象的——它需要混入一個(gè)具體的log方法。因此你必須給方法打上abstract關(guān)鍵字以及override關(guān)鍵字。
10.7 當(dāng)作富接口使用的特質(zhì)
特質(zhì)可以包含大量工具方法,而這些工具方法可以依賴于一些抽象方法來(lái)實(shí)現(xiàn)。例如Scala的Iterator特質(zhì)就利用抽象的next和hasNext定義了幾十個(gè)方法。
trait Logger {
def log(msg: String)
def info(msg: String) { log("INFO: " + msg)}
def warn(msg: String) { log("WARN: "+ msg)}
def severe(msg: String) { log("SEVERE: " + msg)}
}
注意我們是如何把抽象方法和具體方法結(jié)合起來(lái)的。
使用Logger特質(zhì)的類就可以任意調(diào)用這些日志消息方法了,例如:
class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if(amount > balance) severe("Insufficient funds")
else ...
}
...
override def log(msg: String) { println(msg) }
}
10.8 特質(zhì)中的具體字段
特質(zhì)中的字段可以是具體的,也可以是抽象的。如果你給出了初始值,那么字段就是具體的。
trait ShortLogger extends Logged {
val maxLength = 15
...
}
混入該特質(zhì)的類自動(dòng)獲得 一個(gè)maxLength字段。這些字段不是被繼承的;他們只是簡(jiǎn)單地被加到了子類當(dāng)中。
class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
var interest = 0.0
def withdraw(amount: Double) {
if(amount > balance) log("Insufficient funds")
else ...
}
}
假設(shè):
class Account {
var balance = 0.0
}
SavingsAccount繼承了這個(gè)字段,它由所有超類的字段,以及子類中定義的字段構(gòu)成。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-dukLddvd-1662286997134)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210718200828218.png)]
來(lái)自特質(zhì)的字段被放置在子類中
在JVM中,一個(gè)類只能擴(kuò)展一個(gè)超類,因此來(lái)自特質(zhì)的字段不能以相同的方式繼承。
10.9 特質(zhì)中的抽象字段
特質(zhì)中未被初始化的字段在具體的子類中必須被重寫。
不需要寫override
10.10 特質(zhì)構(gòu)造順序
特質(zhì)也有構(gòu)造器,由字段的初始化和其他特質(zhì)體中的語(yǔ)句構(gòu)成。
trait FileLogger extends Logger {
val out =new PrintWriter("app.log") // 特質(zhì)構(gòu)造器的一部分
out.println("# " + new Date().toString) // 特質(zhì)構(gòu)造器的一部分
def log(msg: String) { out.println(msg);out.flush()}
}
這些語(yǔ)句在任何混入該特質(zhì)的對(duì)象在構(gòu)造時(shí)都會(huì)被執(zhí)行。
構(gòu)造器以如下順序執(zhí)行:
先調(diào)用超類的構(gòu)造器特質(zhì)構(gòu)造器在超類的構(gòu)造器之后,類構(gòu)造器之前執(zhí)行特質(zhì)由左到右被構(gòu)造。每個(gè)特質(zhì)當(dāng)中,父特質(zhì)先被構(gòu)造。如果多個(gè)特質(zhì)共有一個(gè)父特質(zhì),而哪個(gè)父特質(zhì)已經(jīng)被構(gòu)造,則不會(huì)被再次構(gòu)造所有特質(zhì)構(gòu)造完畢,子類被構(gòu)造
10.11 初始化特質(zhì)中的字段
特質(zhì)不能由構(gòu)造器參數(shù)。每個(gè)特質(zhì)都有一個(gè)無(wú)參數(shù)的構(gòu)造器。
這個(gè)局限對(duì)于那些需要某種定制才有用的特質(zhì)會(huì)是個(gè)問(wèn)題,例如:
val acct = new SavingsAccount wiht FileLogger("myapp.log") //error
trait FileLogger with Logger {
val filename: String
val out = new PrintStream(filename) // trait先于子類構(gòu)造,會(huì)空指針異常
def log(msg: String) { out.println(msg); out.flush()}
}
解決方法:
val acct = new {
val filename ="myapp.log"
} with SavingsAccount with FileLogger
如果要在類中做同樣的事情,語(yǔ)法:
class SavingsAccount extends {
val filename = "savings.log"
} with Account with FileLogger {
... // SavingsAccount的實(shí)現(xiàn)
}
另一個(gè)解決方法是在FileLogger構(gòu)造器中使用懶值,就像這樣:
trait FileLogger extends Logger {
val filename: String
lazy val out =new PrintStream(filename)
def log(msg: String) { out.println(msg) } //不需要寫override
}
如此一來(lái),out字段將在初次被使用時(shí)才會(huì)被初始化。而在那個(gè)時(shí)候,在filename字段應(yīng)該已經(jīng)被設(shè)置好值了。不過(guò),由于懶值子啊每次使用前都會(huì)檢查是否已經(jīng)初始化,就是不那么高效
10.12 擴(kuò)展類的特性
特質(zhì)可以擴(kuò)展類。這個(gè)類將會(huì)自動(dòng)成為所有混入該特質(zhì)的超類。
trait LoggedException extends Exception with Logged {
def log() {log(getMessage)}
}
創(chuàng)建一個(gè)混入該特質(zhì)的類:
class UnhappyException extends LoggedException {
override def getMessage() = "arggh!"
}
特質(zhì)的超類也自動(dòng)地成為我們的類的超類。
如果我們的類已經(jīng)擴(kuò)展了另一個(gè)類怎么辦?只要是特質(zhì)的超類的一個(gè)子類就沒(méi)關(guān)系
class UnhappyException extends IOException with LoggerdException
不過(guò),如果我們的類擴(kuò)展自一個(gè)不相關(guān)的類,那么就不可能混入這個(gè)特質(zhì)了。
class UnhappyFrame extends JFrame with LoggedException //錯(cuò)誤:不相關(guān)的超類
10.13 自身類型
當(dāng)特質(zhì)擴(kuò)展類時(shí),編譯器能夠確保的一件事就是混入該特質(zhì)的類都認(rèn)為這個(gè)類作為超類。Scala還有另一套機(jī)制可以保證:自身類型(selftype)
當(dāng)特質(zhì)以如下代碼開(kāi)始定義時(shí):
this: 類型 =>
它便只能被混入指定類型的子類。
trait LoggedException extends Logged {
this: Exception =>
def log() {log(getMessage())}
}
注意該特質(zhì)并不擴(kuò)展Exception類,而是有一個(gè)自身類型Exception。這意味著,它只能被混入Exception的子類。
在特質(zhì)的方法中,我們可以調(diào)用該自身類型的任何方法。舉例來(lái)說(shuō),log方法中的getMessage()調(diào)用就是合法的,因?yàn)槲覀冎纓his必定是一個(gè)Exception。
如果你想把這個(gè)特質(zhì)混入一個(gè)不符合自身類型要求的類,就會(huì)報(bào)錯(cuò)。
帶有自身類型的特質(zhì)和帶有超類型的特質(zhì)很相似。兩種情況都能確保混入該特質(zhì)的類能夠使用某個(gè)特定類型的特性。
在某些情況下自身類型這種寫法比超類型版的特質(zhì)更靈活。自身類型可以解決特質(zhì)間的循環(huán)依賴。如果你有兩個(gè)彼此需要的特質(zhì)時(shí)循環(huán)依賴就會(huì)產(chǎn)生。
自身類型也同樣可以處理結(jié)構(gòu)類型——這種類型只給出類必須擁有的方法,而不是類的名稱。以下是使用結(jié)構(gòu)類型的LoggedException定義:
trait LoggedException extends Logged {
this: { def getMessage(): String} =>
def log() {log(getMessage())}
}
這個(gè)特質(zhì)可以被混入任何擁有g(shù)etMessage方法的類
10.14 背后發(fā)生了什么
將類和特質(zhì)翻譯成 JVM的類和接口的對(duì)應(yīng)關(guān)系。
第11章 操作符
11.1 標(biāo)識(shí)符
可以在反引號(hào)中包含幾乎任何字符序列。例如:
val `val` = 42
11.2 中置操作符
a 標(biāo)識(shí)符 b
標(biāo)識(shí)符代表一個(gè)帶有兩個(gè)參數(shù)的方法(一個(gè)隱式的參數(shù)和一個(gè)顯式的參數(shù))
舉例:
class Fraction(n: Int, d: Int) {
private int num =
private int den =
...
def *(other: Fraction) = new Fraction(num * other.num, den * other.den) //定義
}
11.3 一元操作符
a 標(biāo)識(shí)符 等價(jià)于 a.標(biāo)識(shí)符()
如下+-!~可以作為前置操作符,被轉(zhuǎn)換成對(duì)應(yīng)的unary_操作符的方法調(diào)用。
-a 等價(jià)于 a.unary_-
11.4 賦值操作符
a 操作符= b
11.5 優(yōu)先級(jí)
加括號(hào)吧,別記了
11.6 結(jié)合性
所有操作符都是左結(jié)合的,除了:
以冒號(hào)結(jié)尾的操作符賦值操作符
用于構(gòu)造列表的::操作符是右結(jié)合的。例如:
1 :: 2 :: Nil 等價(jià)于 Nil.::(2) 不全
11.7 apply和update方法
f(arg1,arg2,...) = value 等價(jià)于 f.update(arg1,arg2,...,value)
這個(gè)機(jī)制被用于數(shù)組和映射,例如:
val scores = new scala.collection.mutable.HashMap[String,Int]
scores("Bob") = 100
apply方法同樣被經(jīng)常用在伴生對(duì)象中,用來(lái)構(gòu)造對(duì)象而不用顯示地使用new。假定有一個(gè)Fraction類:
class Fraction(n: Int,d: Int) {
...
}
object Fraction {
def apply(n: Int,d : Int) = new Fraction(n,d)
}
因?yàn)橛辛诉@個(gè)apply方法,我們可以用Fraction(3,4)來(lái)構(gòu)造一個(gè)分?jǐn)?shù),而不用new。
11.8 提取器
提取器就是一個(gè)帶有unapply方法的對(duì)象。你可以把unapply方法當(dāng)作是伴生對(duì)象中apply方法的反向操作。unapply方法接受一個(gè)對(duì)象,然后從中提取值,通常這些值都是當(dāng)初用來(lái)構(gòu)造該對(duì)象的值
var Fraction(a,b) = Fraction(3,4) * Fraction(2,5)
object Fraction {
def unapply(input: Fraction) =
if (input.den == 0) None else Some((input.num,input,den))
}
返回的是 Option[(Int,int)]
使用舉例:
val author = "AA BB"
val Name(first,last) = author //unapply
11.9 帶單個(gè)參數(shù)或無(wú)參數(shù)的提取器
沒(méi)有只帶一個(gè)組件的元組。如果unapply方法要提取單值,則它應(yīng)該返回一個(gè)目標(biāo)類型的Option。例如:
object Number {
def unapply(input: String): Option[Int] =
try {
Some(Integer.parseInt(input.trim))
} catch {
case ex: NumberFormatException => None
}
}
用這個(gè)提取器,你可以從字符串中提取數(shù)字:
val Number(n) = "1720"
提取器也可以只是測(cè)試而并不真的將值去除。這樣的話,unapply方法返回的是Boolean。例如:
object IsCompound {
def unapply(input: String) = input.contains(" ")
}
你可以用這個(gè)提取器給模式增加一個(gè)測(cè)試,例如:
author match {
case Name(first,last @ IsCompound()) => ...
case Name(first,last) => ...
}
11.10 unapplySeq方法
提取任何長(zhǎng)度的值的序列,使用unapplySeq方法。返回Option[Seq[A]],其中A是被提取的值的類型。
object Name {
def unapplySeq(input: String): Option[Seq[String]] =
if (input.trim == "") None else Some(input.trim.split("\\s+"))
}
這樣以來(lái)可以匹配任意數(shù)量的變量了:
author match {
case Name(first,last) => ...
case Name(first,middle,last) => ..
case Name(fiset,"van","der",last) => ...
}
第12章 高階函數(shù)
12.1 作為值的函數(shù)
函數(shù)就和數(shù)字一樣??梢栽谧兞恐写娣藕瘮?shù):
import scala.math._
val num = 3.14
val fun = ceil _
ceil函數(shù)后的 _ 意味著你確實(shí)指的是這個(gè)函數(shù),而不是碰巧忘記了給它傳遞參數(shù)
能對(duì)函數(shù)做的兩件事:
調(diào)用它傳遞它,存放在變量中,或者作為參數(shù)傳遞給另一個(gè)函數(shù)
fun(num) // 4.0
fun是一個(gè)包含函數(shù)的變量,而不是一個(gè)固定的函數(shù)。
傳遞給另一個(gè)函數(shù):
Array(3.14,1.42,2.0).map(fun)
map方法接受一個(gè)函數(shù)參數(shù),將它應(yīng)用到數(shù)組中的所有值,然后返回結(jié)果的數(shù)組。
12.2 匿名函數(shù)
你不需要給每一個(gè)函數(shù)命名,正如你不需要給每個(gè)數(shù)字命名一樣。以下是一個(gè)匿名函數(shù):
(x: Double) => 3*x
存放在變量
val triple = (x: Double) => 3 * x 等價(jià)于
def tripe(x: Double) = 3 * x
Array(3.14,1.42,2.0).map((x: Double)=> 3*x)
12.3 帶函數(shù)參數(shù)的函數(shù)
def valueAtOneQuartr(f: (Double) => Double) = f(0.25)
例如:
valueAtOneQuarter(ceil _) // 1.0
valueAtOneQuarter(sqrt _) // 0.5
接受函數(shù)參數(shù)的函數(shù),稱為高階函數(shù)
高階函數(shù)產(chǎn)出另一個(gè)函數(shù):
def mulBy(factor: Double) = (x: Double) => factor * x
val quintuple = mulBy(5)
quintupe(20) // 100
12.4 參數(shù)(類型)推斷
可以簡(jiǎn)寫:
valueAtOneQuarter((x) => 3*x)
只有一個(gè)參數(shù)的函數(shù),可以再略去():
valueAtOneQuarter(x => 3*x)
如果參數(shù)在=>右側(cè)只出現(xiàn)一次,你可以用_替換掉它:
valueAtOneQuarter(3 * _)
一個(gè)將某值乘以3的函數(shù)
請(qǐng)注意這些簡(jiǎn)寫方式僅在參數(shù)類型已知的情況下有效:
val fun = 3 * _ //error
val fun = 3 * (_: Double) // correct
val fun: (Double) => Double = 3 * _ // fun的類型是(Double)=>Double , _的類型就是 Double
12.5 一些有用的高階函數(shù)(最大差別:用函數(shù)定義另一個(gè)函數(shù))
(1 to 9).map("*" * _).foreach(println _)
filter方法輸出所有匹配某個(gè)特定條件的元素。
(1 to 9).filter(_ % 2 == 0)
reduceLeft方法接受一個(gè)二元的函數(shù)——即一個(gè)帶有兩個(gè)參數(shù)的函數(shù)——并將它應(yīng)用到序列中的所有元素,從左到右。例如:
(1 to 9).reduceLeft(_ * _) // 等價(jià)于 1*2*3*4*5*6*7*8*9
做排序:
"Mary has a little lamb".split(" ").sortWith(_.length < _.length)
//輸出長(zhǎng)度遞增排序的數(shù)組: Array("a","had","Mary","lamb","little")
12.6 閉包
可以在任何作用域內(nèi)定義函數(shù):包、類甚至是另一個(gè)函數(shù)或方法。
在函數(shù)體內(nèi),你可以訪問(wèn)到相應(yīng)作用域內(nèi)的任何變量。你的函數(shù)可以在變量不再處于作用域內(nèi)時(shí)被調(diào)用。
例如:
def mulBy(factor: Double) = (x: Double) => factor * x
val tripe = mulBy(3)
val half = mulBy(0.5)
println(triple(14)) + “ ” + half(14)
每一個(gè)返回的函數(shù)都有自己的factor設(shè)置。
這樣一個(gè)函數(shù)都有自己的factor設(shè)置。
這樣的一個(gè)函數(shù)被稱為閉包。閉包由代碼和代碼用到的任何非局部變量定義構(gòu)成。
這些函數(shù)實(shí)際上是以類的對(duì)象方式實(shí)現(xiàn) 的,該類有一個(gè)實(shí)例變量factor和一個(gè)包含了函數(shù)體的apply方法。
12.7 SAM轉(zhuǎn)換
告訴另一個(gè)函數(shù)做某件事時(shí)。隱式轉(zhuǎn)換。 TUDO。
12.8 柯里化
指的是將原來(lái)接受兩個(gè)參數(shù)的函數(shù)變成新的接受一個(gè)參數(shù)的函數(shù)的過(guò)程。新的函數(shù)返回一個(gè)以原有第二個(gè)參數(shù)作為參數(shù)的函數(shù)。
def mul(x: Int, y: Int) = x * y
def mulOneAtATime(x: Int) = (y: Int) => x*y
要計(jì)算兩個(gè)乘積,調(diào)用:
mulOneAtATime(6)(7)
支持簡(jiǎn)寫定義柯里化函數(shù):
def mulOneAtATime(x: Int)(y: Int) = x * y
典型例子:
val a = Array("Hello","World")
val b = Array("hello","world")
a.corresponds(b)(_.equalsIgnoreCase(_))
def corresponds[B](that: Seq[B])(p: (A,B) => Boolean): Boolean
先確定了B,再確定A
12.9 控制抽象
可以將一系列語(yǔ)句歸組成不帶參數(shù)也沒(méi)有返回值的函數(shù)。舉例:
def runInThread(block: ()=> Unit) {
new Thread {
override def run() {block()}
}.start()
}
使用:
runInThread{ () => println("Hi");Threads.sleep(1000);println("Bye")}
要想在調(diào)用中省去()=> ,可以使用換名調(diào)用表示法:在參數(shù)聲明和調(diào)用該函數(shù)參數(shù)的地方省略(),但保留=>:
def runInThread(block: => Unit) {
new Thread {
override def run() { block }
}.strat()
}
使用變成:
runInthread{ println("Hi"); Threads.sleep(1000);println("Bye")}
Scala可以構(gòu)建控制抽象:看上去像是變成語(yǔ)言的關(guān)鍵字的函數(shù)。完全可以定義一個(gè)像while函數(shù)那樣的,比如定義一個(gè)until函數(shù):
def until(condition: => Boolean)(block: => Unit) {
if (!condition) {
block
until(conditon)(block)
}
}
var x = 10
until(x == 0) {
x -= 1
println(x)
}
這樣的函數(shù)參數(shù)有一個(gè)專業(yè)術(shù)語(yǔ)叫做換名調(diào)用參數(shù)。和一個(gè)常規(guī)的參數(shù)不同,函數(shù)在被調(diào)用時(shí),參數(shù)表達(dá)式不會(huì)被求值。
12.10 return表達(dá)式
不需要使用return語(yǔ)句來(lái)返回函數(shù)值。函數(shù)的返回值就是函數(shù)體的值。
不過(guò),你可以用return來(lái)從一個(gè)匿名函數(shù)中返回值給包含整個(gè)匿名函數(shù)的帶名函數(shù)。這對(duì)于控制抽象是很有用的。
例如:
def indexOf(str: String, ch: Char): Int = {
var i = 0
until (i == str.length) {
if(str(i) == ch) return i
i += 1
}
return -1
}
直接終止包含它的帶名函數(shù)。
如果你要在帶名函數(shù)中使用return的話,則需要給出其返回類型。舉例來(lái)說(shuō),就是在上述的indexOf函數(shù)中,編譯器沒(méi)法推斷出它會(huì)返回Int。
控制流程的實(shí)現(xiàn)依賴一個(gè)在匿名函數(shù)的return表達(dá)式中拋出的特殊異常,該異常從until函數(shù)傳出,并被indexOf函數(shù)捕獲。
注意:如果異常在被送往帶名函數(shù)值前,在一個(gè)try代碼塊中被捕獲掉了,那么相應(yīng)的值就不會(huì)被返回。
第13章 集合
13.1 主要的集合特質(zhì)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-45IqjpMr-1662286997135)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719094927962.png)]
Iterable指的是那些能生成用來(lái)訪問(wèn)集合中所有元素的Iterator的集合:
val coll = ... //某種Iterable
val iter = coll.iterator
while (iter.hasNext) {
對(duì) iter.next() // 執(zhí)行某種操作
}
Seq是一個(gè)有先后次序的值的序列,比如數(shù)組或列表。IndexedSeq允許我們通過(guò)整型的下標(biāo)快速地訪問(wèn)任意元素。舉例來(lái)說(shuō),ArrayBuffer是帶下標(biāo)地,但鏈表不是。
Set是一組沒(méi)有先后次序地值。在SortedSet中,元素是以某種排過(guò)序地順序被訪問(wèn)。
Map是一組(鍵,值)對(duì)偶。SortedMap按照鍵地排序訪問(wèn)其中地實(shí)體。
每個(gè)Scala集合特質(zhì)或類都有一個(gè)帶有apply方法的伴生對(duì)象,這個(gè)apply方法可以用來(lái)構(gòu)建該集合中的實(shí)例。例如:
Iterable(0xFF,0xFF00,0xFF0000)
Set(Color.RED,Color.GREEN,Color.BLUE)
Map(Color.RED -> 0xFF0000, Color.GREEN -> 0xFF00, Color.BLUE -> 0xFF)
SortedSet("Hello","World")
13.2 可變和不可變集合
不可變的集合從不改變,因此你可以安全地共享其引用,甚至是一個(gè)多線程的應(yīng)用當(dāng)中。舉例來(lái)說(shuō),既有scala.collection.mutable.Map,也有scala.collection.immutable.Map。他們有一個(gè)共有的超類型scala.collection.Map(當(dāng)然,這個(gè)超類型沒(méi)有定義任何改值操作)
Scala優(yōu)先采用不可變集合。scala.collection包中的伴生對(duì)象產(chǎn)出不可變的集合。舉例來(lái)說(shuō),scala.collection.Map("Hello"->42)是一個(gè)不可變的映射。
不止如此,總被引入的scala包和Predef對(duì)象里有指向不可變特質(zhì)的類型別名List、Set和Map
舉例來(lái)說(shuō),Predef.Map和scala.collection.immuatable.Map是一回事
不可變集合的用途:基于老的集合創(chuàng)建新的集合
如果某個(gè)值已經(jīng)在集中,則你得到的是指向老集的引用。這在遞歸計(jì)算中特別自然。舉例來(lái)說(shuō),你可以計(jì)算某個(gè)整數(shù)中所有出現(xiàn)過(guò)的數(shù)的集:
def digits(n: Int): Set[Int] =
if ( n< 0) digits(-n)
else if ( n<10) Set(n)
else digits(n/10) + (n % 10) // 注意:這里的加號(hào)不在 IF-ELSE中
13.3 序列
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-Dy1YeqzA-1662286997136)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719101219354.png)]
Vector是ArrayBuffer的不可變版本:一個(gè)帶下標(biāo)的序列,支持快速的隨機(jī)訪問(wèn)。向量是以樹(shù)形結(jié)構(gòu)的形式實(shí)現(xiàn)的,每個(gè)節(jié)點(diǎn)可以有不超過(guò)32個(gè)子節(jié)點(diǎn)。對(duì)于一個(gè)有100萬(wàn)個(gè)元素的向量而言,我們只需要四層節(jié)點(diǎn)(10^6 ~= 32^4)。訪問(wèn)這樣一個(gè)列表中的某個(gè)元素只需要4跳,而在鏈表中,同樣的操作平均需要50W跳。
Range表示一個(gè)整數(shù)序列,Range對(duì)象并不存儲(chǔ)所有值而只是起始值,結(jié)束值和增值。你可以用to和until方法來(lái)構(gòu)造Range對(duì)象。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-1ufTFi4q-1662286997136)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719101932425.png)]
我們?cè)诘?章中介紹了數(shù)組緩沖。而棧、隊(duì)列、優(yōu)先級(jí)隊(duì)列等都是標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu),用來(lái)實(shí)現(xiàn)特定的算法。
13.4 列表
在Scala中,列表要么是Nil(即空表),要么是一個(gè)head元素加上一個(gè)tail,而tail又是一個(gè)列表。比如:
val digits = List(4,2)
digits.head // 值為4
digits.tail 是 List(2)
digits.tail.head // 是2
digits.tail.tail // 是Nil
::操作符從給定的頭和尾創(chuàng)建一個(gè)新的列表。例如:
9 :: List(4,2) // List(9,4,2)
9 :: 4 :: 2 :: Nil // 右結(jié)合,從末端開(kāi)始
9 :: (4 :: (2 :: Nil)
在scala中,鏈表的遞歸會(huì)比用迭代器自然:
def sum(lst: List[Int]) : Int =
if ( lst == Nil) 0 else lst.head + sum(lst.tail)
或者模式匹配:
def sum(lst: List[Int]): Int = lst match {
case Nil => 0
case h :: t => h + sum(t) // h是lst.head 而 t是lst.tail
}
或者:
List(9,4,2).sum
13.5 可變列表
LinkedList,只不過(guò)你可以通過(guò)對(duì)elem引用賦值來(lái)修改其頭部,對(duì)next引用賦值來(lái)修改其尾部。
注意:你并不是給head和tail賦值
例子,把所有負(fù)值改為零:
val lst = scala.collection.mutable.LinkedList(1,-2,7,-9)
var cur = lst
while ( cur != Nil) {
if (cur.elem < 0) cur.elem = 0
cur = cur.next
}
var cur = lst
while( cur != Nil && cur.next != Nil) {
cur.next = cur.next.next
cur = cur.next
}
變量cur用起來(lái)就像是迭代器,但實(shí)際上它的類型是LinkedList
除了LinkedList外,Scala還提供了一個(gè)DoubleLinkedList,區(qū)別是它多帶一個(gè)prev引用。
注意:如果你想要把列表中某個(gè)節(jié)點(diǎn)變成列表中的最后一個(gè)節(jié)點(diǎn),你不能將next引用設(shè)為Nil,而應(yīng)該將它設(shè)為L(zhǎng)inkedList.empty。也不要將它設(shè)為null,不然你會(huì)在遍歷該鏈表的時(shí)候遇到空指針錯(cuò)誤。
13.6 集
集是不重復(fù)元素的集合。底層哈希,順序不一定。其元素根據(jù)hashCode方法的值進(jìn)行組織。
Set(1,2,3,4,5,6) // 可能訪問(wèn)到的次序 5,2,3,1,6
鏈?zhǔn)焦<梢杂涀≡乇徊迦氲捻樞?。它?huì)維護(hù)一個(gè)鏈表來(lái)達(dá)到這個(gè)目的,例如:
val weekdays = scala.collection.mutable.LinkedHashSet("MP","TU","WE")
如果你想要按照已排序的順序來(lái)訪問(wèn)集中的元素,用已排序的集:
scala.collection.immutable.SortedSet(1,2,3,4,5,6)
注意:Scala 2.9 沒(méi)有可變的已排序集。如果你需要這樣一個(gè)數(shù)據(jù)結(jié)構(gòu),可以用java.util.TreeSet
位集(bit set)是集的一種實(shí)現(xiàn),以一個(gè)字位序列的方式存放非負(fù)整數(shù)。如果集中有i,則第i位是1。Scala提供了可變的和不可變的兩個(gè)BitSet類。
contains方法檢查某個(gè)集是否包含了給定的值。subsetOf方法檢查某個(gè)集當(dāng)中的所有元素是否都被另一個(gè)集包含。
val digits = Set(1,7,2,9)
digits contains 0 // false
Set(1,2) subsetOf digits // true
union、intersect和diff方法執(zhí)行通常的集合操作。如果愿意,可以寫成|、&、&~。也可以將union寫成++,將diff寫成--。舉例:
val primes = Set(2,3,5,7)
digits union primes // Set(1,2,3,5,7,9)
digits & primes // Set(2,7)
digits -- primes // Set(1,9)
13.7 用于添加或去除元素的操作符
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-TkrrpcHE-1662286997136)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719111906748.png)]
+用于將元素添加到無(wú)先后次序的集合,而+:和:+則是將元素添加到有先后次序的集合的開(kāi)頭或結(jié)尾。
13.8 常用方法
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-fHQmpYTE-1662286997137)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719112309642.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-hjhLnIbc-1662286997138)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719112319262.png)]
Seq特質(zhì)在Iterable特質(zhì)的基礎(chǔ)上又額外添加了一些方法。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-pcjazEPW-1662286997138)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719112408985.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-iPDlUoim-1662286997138)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719112415683.png)]
13.9 將函數(shù)映射到集合
map函數(shù)。
如果函數(shù)產(chǎn)出一個(gè)集合而不是單個(gè)值的話,你可能會(huì)想要將所有的值串接在一起。如果有這個(gè)要求,則用flatMap。例如:
def ulcase(s: String) = Vector(s.toUpperCase(), s.toLowerCase())
則 names.map(ulcase)得到:
List(Vector("PETER","peter"),Vector("PAUL","paul"),Vector("MARY","mary"))
而names.flatMap(ulcase)得到:
List("PETER","peter","PAUL","paul","MARY","mary")
collect方法用于偏函數(shù)——那些并沒(méi)有對(duì)所有可能的輸入值進(jìn)行定義的函數(shù)。它產(chǎn)出被定義的所有參數(shù)的函數(shù)值的jihe.liru:
"-3+4".collect { case '+' => 1; case '-' => -1} //Vector(-1,1)
最后,如果你應(yīng)用函數(shù)到各個(gè)元素僅僅是為了它的副作用而不關(guān)心函數(shù)值的話,可以用foreach:
names.foreach(println)
13.10 化簡(jiǎn)、折疊和掃描
map方法將一元函數(shù)應(yīng)用到集合的所有元素。如果要二元函數(shù),類似c.reduceLeft(op)這樣的調(diào)用將op相繼應(yīng)用到元素:
List(1,7,2,9).reduceLeft(_ - _)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-dlqvEiQy-1662286997139)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719113700704.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-tzTEodim-1662286997139)(C:\Users\johnqyang\AppData\Roaming\Typora\typora-user-images\image-20210719113707052.png)]
reduceRight方法做同樣的事,只不過(guò)它從集合的尾部開(kāi)始。例如:
List(1,7,2,9).reduceRight(_ - _)
1-(7-(2-9))
以不同于集合首元素的初始元素開(kāi)始計(jì)算通常也很有用。對(duì)coll.foldLeft(init)(op)的調(diào)用:
List(1,7,2,9).foldLeft(0)(_ - _)
// 0 - 1 - 7 - 2 - 9 = -19
也可以用:/操作符來(lái)寫foldLeft操作,像這樣:
(0 /: List(1,7,2,9))(_ - _)
折疊有時(shí)候可以作為循環(huán)的替代。例如:
val freq = scala.collection.mutable.Map[Char,Int]()
for ( c <- "Mississippi") freq(c) = freq.getOrElse(c,0) + 1
換種思路,將頻率映射和新遇到的字母結(jié)合在一起,產(chǎn)生一個(gè)新的頻率映射。這就是折疊:
(Map[Char,Int]() /: "Mississippi") {
(m,c) => m + (c -> getOrElse(c,0) +1))
}
每一步計(jì)算出一個(gè)新的映射
任何while循環(huán)都可以用折疊來(lái)代替。構(gòu)建一個(gè)把循環(huán)中被更新的所有變量結(jié)合在一起的數(shù)據(jù)結(jié)構(gòu),然后定義一個(gè)操作,實(shí)現(xiàn)循環(huán)中的一步。
最后,在scanLeft和scanRight方法將折疊和映射操作結(jié)合在一起。你得到的是包含素有中間結(jié)果的集合。例如:
(1 to 10).scanLeft(0)(_ + _)
// 結(jié)果:
Vector(0,1,3,6,10,15,21,28,36,45,55)
13.11 拉鏈操作
前一節(jié)的方法是將操作應(yīng)用到同一集合中的相鄰元素。有時(shí),兩個(gè)集合需要結(jié)合在一起。
val prices = List(5.0,20.0,9.95)
val quantities = List(10,2,1)
zip方法讓你將他們組合成一個(gè)個(gè)對(duì)偶的列表。例如:
prices zip quantities
// 結(jié)果
List[(Double,Int)] = List((5.0,10),(20.0,2),(9.95,1))
應(yīng)用函數(shù):
(prices zip quantities) map { p => p._1 * p._2}
// 結(jié)果
List(50.0,40.0,9.95)
// sum
((prices zip quantities) map { p => p._1 * p._2})sum
如果一個(gè)集合比另一個(gè)集合短,那么結(jié)果中的對(duì)偶數(shù)量和較短的那個(gè)集合的元素?cái)?shù)量相同。例如:
List(5.0,20.0,9.95) zip List(10,2)
// 結(jié)果
List((5.0,10),(20.0,2))
zipAll方法讓你指定較短列表的缺省值:
List(5.0,20.0,9.95).zipAll(List(10,2),0.0,1)
//結(jié)果
List((5.0,10),(20.0,2),(9.95,1))
zipWithIndex方法返回對(duì)偶的列表,其中每個(gè)對(duì)偶中第二個(gè)組成部分是每個(gè)元素的下標(biāo)。例如:
"Scala".zipWithIndex
// 結(jié)果
Vector(('S',0),('c',1),('a',2),('1',3),('a',4))
// 計(jì)算具備某種屬性的元素的下標(biāo)
"Scala".zipWithIndex.max
"Scala".zipWithIndex.max._2
13.12 迭代器
iterator方法從集合中得到一個(gè)迭代器。
Iterable中有一些方法可以產(chǎn)出迭代器,比如grouped或sliding
有了迭代器,可以用next和hasNext方法來(lái)遍歷集合中的元素了。
while(iter.hasNext)
對(duì) iter.next() 執(zhí)行某種操作
//--------------
for( elem <- iter)
對(duì)elem執(zhí)行某種操作
上述兩種操作迭代器都會(huì)移動(dòng)到集合的末尾,此后就不再被使用了。
Iterator類定義了一些與集合方法使用起來(lái)完全相同的方法。。具體而言,除了13.8節(jié)列出的所有Iterable的方法,除了head,headOption,last,lastOption,tail,init,takeRight和dropRight外,都支持。再調(diào)用諸如map,filter,count,sum甚至是length方法后,迭代器將位于集合的尾端,你不能再繼續(xù)使用它。而對(duì)于其他方法,比如find或take,迭代器位于已找到元素或已取得元素之后。
如果感覺(jué)操作迭代器很繁瑣,可以使用toArray,toIterable,toSeq,toSet或toMap來(lái)將相應(yīng)的值拷貝到一個(gè)新的集合中
13.13 流
13.14 懶視圖
流方法是懶執(zhí)行的,僅當(dāng)結(jié)果被需要時(shí)才計(jì)算。可以對(duì)其他集合應(yīng)用view方法來(lái)得到類似的效果。該方法產(chǎn)出一個(gè)其方法總是被懶執(zhí)行的集合。例如:
val powers = (0 until 1000).view.map(pow(10,_))
將產(chǎn)出一個(gè)未被未被求值的集合。(不像流,這里連第一個(gè)元素都未被求值。)當(dāng)你執(zhí)行:
powers(100)
pow(10,100)被計(jì)算,但其他值的冪并沒(méi)有被計(jì)算。和流不同,這些視圖并不緩存任何值。如果你再次調(diào)用powers(100),pow(10,100)講被重新計(jì)算。
和流一樣,用force方法可以對(duì)懶視圖強(qiáng)制求值。你將得到與原集合相同類型的新集合。
懶集合對(duì)于處理那種需要以多種方式進(jìn)行變換的大型集合是很有好處的,因?yàn)樗苊饬藰?gòu)建出大型中間集合的需要。比較:
(0 to 1000).map(pow,_).map(1 / _)
(0 to 1000).view.map(pow(10,_)).map(1 / _).force
前一個(gè)將會(huì)計(jì)算出10的n次方的集合,然后再對(duì)每一個(gè)得到的值取倒數(shù)。而后一個(gè)產(chǎn)出的是記住了兩個(gè)map操作的視圖。當(dāng)求值動(dòng)作被強(qiáng)制執(zhí)行時(shí),對(duì)于每個(gè)元素,這兩個(gè)操作被同時(shí)執(zhí)行,不需要額外構(gòu)建中間集合。
13.15 與java集合的互操作
13.16 線程安全的集合
多線程訪問(wèn)一個(gè)可變集合,需確保不會(huì)在其他線程對(duì)正在訪問(wèn)的進(jìn)行修改。Scala類庫(kù)提供了六個(gè)特質(zhì),你可以將他們混入集合,讓集合的操作變成同步的:
SynchronizedBuffer
SynchronizedMap
SynchronizedPriorityQueue
SynchronizedQueue
SynchronizedSet
SynchronizedStack
舉例:
val scores = new scala.collection.mutable.HashMap[String,Int] with scala.collection.mutable.SynchronizedMap[String,Int]
通常來(lái)說(shuō),你最好使用java.util.concurrent包中的某個(gè)類。舉例來(lái)說(shuō),如果多線程共享一個(gè)映射,那么就用ConcurrentHashMap或ConcurrentSkipListMap。這些集合比簡(jiǎn)單地用同步方式執(zhí)行所有方法地映射更高效。不同線程可以并發(fā)地訪問(wèn)數(shù)據(jù)結(jié)構(gòu)中互不相關(guān)地部分。(請(qǐng)勿嘗試自己實(shí)現(xiàn)?。┏酥?,對(duì)應(yīng)地迭代器是“弱一致性”的,意思是說(shuō),它提供的視圖是迭代器被獲取時(shí)數(shù)據(jù)結(jié)構(gòu)的樣子。
正如前一個(gè)節(jié)描述的,可以將java.util.concurrent中的集合轉(zhuǎn)換成Scala集合來(lái)使用。
13.17 并行集合
如果coll是個(gè)大型集合,則:
coll.par.sum
上述代碼會(huì)并發(fā)地對(duì)它求和。par方法會(huì)產(chǎn)出當(dāng)前集合的一個(gè)并行實(shí)現(xiàn)。該實(shí)現(xiàn)會(huì)盡可能的并行執(zhí)行集合方法。例如:
coll.par.count(_ % 2 == 0)
對(duì)數(shù)據(jù),緩沖,哈希表,平衡樹(shù)而言,并行實(shí)現(xiàn)會(huì)直接重用底層實(shí)際集合的實(shí)現(xiàn),而這是很高效的。
可以通過(guò)對(duì)要遍歷的集合應(yīng)用.par并行化for循環(huán),就像這樣:
for(i <- (0 until 100).par) print(i + " ")
而在for/yield循環(huán)中,結(jié)果是依次組裝的。
for ( i <- (0 until 100).par) yield i + " "
par方法返回的并行集合的類型為擴(kuò)展自ParSeq,ParSet,或ParMap特質(zhì)的類型,所有的這些特質(zhì)都是ParIterable的子類型。這些并不是Iterable的子類型,因此你不能將并行集合傳遞給預(yù)期Iterable、Seq、Set或Map的方法。你可以用ser方法將并行集合轉(zhuǎn)換回串行版本,也可以實(shí)現(xiàn)接受通用的GenIterable、GenSeq、GenSet、GenMap類型的參數(shù)的方法。
說(shuō)明并不是所有的方法都可以被并行化。
第14章 模式匹配和樣例類
總結(jié)TIPS:
case _ if Character.isDigit(ch) 。模式匹配的守衛(wèi) 模式中的變量:匹配的表達(dá)式會(huì)被賦值給那個(gè)變量 可以對(duì)表達(dá)式的類型進(jìn)行匹配 匹配數(shù)組的內(nèi)容。本質(zhì)是提取器機(jī)制——帶有從對(duì)象中提取值的unapply或unapplySeq方法的對(duì)象。 變量聲明中也可以用模式匹配。左邊是變量,右邊是對(duì)應(yīng)的匹配。 for推導(dǎo)式中也可以使用模式匹配。典型的就是(k,v)。 樣例類是一種特殊的類,它們經(jīng)過(guò)優(yōu)化以被用于模式匹配。 樣例類與樣例對(duì)象: abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object Nothing extends Amount
amt match {
case Dollar(v) => "$" + v
case Currency(_,u) => "I got" + u
case Nothing => ""
}
//樣例類使用(),樣例對(duì)象不使用圓括號(hào)
匹配嵌套結(jié)構(gòu),使用-*表示多個(gè)。并使用@表示法將嵌套的值綁定到變量。art @ Article(_,_)。
14.1 更好的switch
var sign = ...
val ch: Char = ...
ch match {
case '+' => sign = 1
case '-' => sign = -1
case _ => sign = 0
}
_等價(jià)于default , 如果沒(méi)有模式能匹配,代碼會(huì)拋出MatchError。
Scala的模式匹配沒(méi)有break也不會(huì)進(jìn)行下一個(gè)case。
由于Scala是函數(shù)式編程,表達(dá)式也是值,則:
sign = ch match {
case '+' => 1
case '-' => -1
case _ => 0
}
可以在match表達(dá)式中使用任何類型,而不僅僅是數(shù)字。例如:
color match {
case Color.RED => ...
case Color.BLACK => ...
...
}
14.2 守衛(wèi)
ch match {
case '+' => sign = 1
case '-' => sign = -1
case _ if Character.isDigit(ch) => digit = Character.digit(ch,10)
case _ => sign = 0
}
守衛(wèi)可以是任何Boolean條件。就是讓某些滿足條件的獨(dú)立成一個(gè)。不像C++中 要是全部都是數(shù)字得全部列出。
注意模式總是從上往下進(jìn)行匹配。
14.3 模式中的變量
如果case關(guān)鍵字后面跟著一個(gè)變量名,那么匹配的表達(dá)式會(huì)被賦值給那個(gè)變量。例如:
str(i) match {
case '+' => sign = 1
case '-' => sign = -1
case ch => digit = Character.digit(ch,10)
}
你可以將case _ 看作是這個(gè)特性的一種特殊情況,只不過(guò)變量名是_罷了。
你可以在守衛(wèi)中使用這個(gè)變量:
str(i) match {
case ch if Character.isDigit(ch) => digit = Character.digit(ch,10)
...
}
注意:變量模式可能會(huì)與常量表達(dá)式相沖突,例如:
import scala.math._
x match {
case Pi => ....
...
}
Scala是如何知道Pi是常量,而不是變量的呢?背后的規(guī)則是,變量必須以小寫字母開(kāi)頭。
如果你有一個(gè)小寫字母開(kāi)頭的常量,則需要將它包在反引號(hào)里:
import java.io.File._
str match {
case `pathSeparator` => ...
...
}
14.4 類型模式
對(duì)表達(dá)式的類型進(jìn)行匹配,例如:
obj match {
case x: Int => x
case s: String => Integer.parseInt(s)
case _: BigInt => Int.MaxValue
case _ => 0
}
更傾向于使用這樣的模式匹配,而不是isInstanceOf操作符。
注意:當(dāng)你在類型匹配的時(shí)候,必須給出一個(gè)變量名。否則,將會(huì)拿對(duì)象本身來(lái)進(jìn)行匹配:
obj match {
case _: BigInt => Int.MaxValue //匹配任何類型為BigInt的對(duì)象
case BigInt => -1 //匹配類型為Class的BigInt對(duì)象
}
注意:匹配發(fā)生在運(yùn)行期,java虛擬機(jī)中泛型的類型信息是被擦掉的。因此,你不能用類型來(lái)匹配特定的Map類型。
case m: Map[String,Int] => ... // 別這樣做
case m: Map[_, _] => ... // OK
但是對(duì)于數(shù)組而言其類型信息是完好的??梢云ヅ銩rray[Int]
14.5 匹配數(shù)組、列表和元組
匹配數(shù)組,使用Array表達(dá)式:
arr match {
case Array(0) => "0"
case Array(x,y) => x + " " + y
case Array(0, _*) => "0 ..."
case _ => "something else"
}
第一個(gè)模式匹配包含0的數(shù)組。第二個(gè)模式匹配任何帶有兩個(gè)元素的數(shù)組,并將這兩個(gè)元素分別綁定到變量x和y。第三個(gè)表達(dá)式匹配任何以零開(kāi)始的數(shù)組。
匹配列表,同樣的方式。可以使用List表達(dá)式,或使用::操作符:
lst match {
case 0 :: Nil => "0"
case x ::y :: Nil => x + " " + y
case 0 :: tail => "0 ..."
case _ => "something" else
}
對(duì)于元組:
pair match {
case (0, _) => "0 ..."
case (y,0) => y + " 0"
case _ => "neither is 0"
}
請(qǐng)注意變量是如何綁到列表或元組的不同部分的。由于這種綁定讓你可以很輕松地訪問(wèn)復(fù)雜結(jié)構(gòu)地各個(gè)組成部分,因此這樣地操作被稱為”析構(gòu)“。
14.6 提取器
匹配的原理:提取器機(jī)制——帶有從對(duì)象中提取值的unapply或unapplySeq方法的對(duì)象。這些方法的實(shí)現(xiàn)在第11章。unapply方法用于提取固定數(shù)量的對(duì)象;而unapplySeq提取的是一個(gè)序列,可長(zhǎng)可短。
例如:
arr match {
case Array(0,x) => ...
...
}
Array伴生對(duì)象就是一個(gè)提取器——它定義了一個(gè)unapplySeq方法。該方法被調(diào)用時(shí),是以被執(zhí)行匹配動(dòng)作的表達(dá)式作為參數(shù),而不是模式中看上去像是參數(shù)的表達(dá)式。Array.unapplySeq(arr)產(chǎn)出一個(gè)序列值,即數(shù)組中的值。第一個(gè)值與零進(jìn)行比較,而第二個(gè)被賦值給x。
正則表達(dá)式是另一個(gè)適用于使用提取器的場(chǎng)景。如果正則表達(dá)式有分組,你可以用提取器來(lái)匹配每個(gè)分組。例如:
val pattern = "([0-9]+) ([a-z]+)".r
"99 bottles" match {
case pattern(num,item) => ... //將num設(shè)為"99" , item設(shè)為“bottles”
}
注意,這里的提取器并非是一個(gè)伴生對(duì)象,而是一個(gè)正則表達(dá)式對(duì)象。
14.7 變量聲明中的模式
變量聲明中也可以使用這樣的模式。例如:
val (x,y) = (1,2)
同時(shí)把x=1,y=2。這對(duì)于返回對(duì)偶的函數(shù)很有用:
val (q,r) = BigInt(10) /% 3
也可以用于任何帶有變量的模式。例如:
val Array(first,secont, _*) = arr
上述代碼將數(shù)組arr的第一個(gè)和第二個(gè)元素分別賦值給first和secong。
14.8 for表達(dá)式的模式
for推導(dǎo)式中使用帶變量的模式。對(duì)每一個(gè)遍歷到的值,這些變量都會(huì)被綁定。這使得我們可以方便地遍歷映射:
import scala.collection.JavaConversions.propertiesAsScalaMap
// java Properties -> Scala映射
for ((k,v) <- System.getProperties()) {
println(k + " -> " v)
}
在for推導(dǎo)式中,如果匹配失敗,會(huì)被忽略。
for ((k,v) <- System.getProperties() if v == "") {
println(k)
}
14.9 樣例類
特殊的類,經(jīng)過(guò)優(yōu)化以被用于模式匹配。例子,兩個(gè)擴(kuò)展自常規(guī)類的樣例類:
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
你也可以針對(duì)單例的樣例對(duì)象:
case object Nothing extends Amount
當(dāng)我們有一個(gè)類型為Amount的對(duì)象時(shí),我們可以用模式匹配來(lái)匹配到它的類型,并將屬性值綁定到變量:
amt match {
case Dollar(v) => "$" + v
case Currency(_, u) => "Oh noes, I got " + u
case Nothing => ""
}
說(shuō)明:樣例類的實(shí)例使用(),樣例對(duì)象不使用圓括號(hào)。
當(dāng)你聲明樣例類時(shí),有如下幾件事自動(dòng)發(fā)生:
構(gòu)造器中的每一個(gè)參數(shù)都成為val —— 除非它被顯式地聲明為var(不建議這樣做)在伴生對(duì)象中提供apply方法讓你不用new關(guān)鍵字就能構(gòu)造出相應(yīng)的對(duì)象,比如Dollar(29.9)提供unapply方法讓模式匹配可以工作——參照第11章將生成toString,equals、hashCode和copy方法——除非顯式地給出這些方法地定義。
除了上述外,樣例類和其他類完全一樣。你可以添加字段和方法,擴(kuò)展他們等等
14.10 copy方法和帶名參數(shù)
樣例類的copy方法創(chuàng)建一個(gè)與現(xiàn)有對(duì)象值相同地新對(duì)象。例如:
val amt = Currency(29.9,"EUR")
val price = amt.copy()
可以使用帶名參數(shù)修改某些屬性:
val price = amt.copy(value = 19.95)
val price = amt.copy(unit = "CHF")
14.11 case語(yǔ)句中的中置表示法
如果unapply方法產(chǎn)出一個(gè)對(duì)偶,則你可以在case語(yǔ)句中使用中置表示法。尤其是,對(duì)于有兩個(gè)參數(shù)的樣例類,你可以使用中置表示法來(lái)表示它。例如:
amt match { case a Currency u => ...} //等同于 case Currency(a,u)
這個(gè)特性本意是要匹配序列。舉例來(lái)說(shuō),每個(gè)List對(duì)象要么是Nil,要么是樣例類::,定義如下:
case class ::[E](head: E, tail: List[E]) extends List[E]
因此,可以這樣寫:
lst match { case h :: t => ...}
//等價(jià)于case ::(h,t),將調(diào)用::.unapply(lst)
解析結(jié)果組合在一起的~樣例類。本意同樣是中置表達(dá)式的形式用于case語(yǔ)句:
result match { case p~q => ...} //等同于 case ~(p,q)
當(dāng)你把多個(gè)中置表達(dá)式放在一起的時(shí)候,他們會(huì)更易讀。例如:
result match { case p ~ q ~ r => ...}
等價(jià)于~(~(p,q),r)
如果操作符是以冒號(hào)結(jié)尾,則它是從右向左結(jié)合的。例如:
case first :: second :: rest
case ::(first,::(second,rest))
14.12 匹配嵌套結(jié)構(gòu)
樣例類經(jīng)常被用于嵌套結(jié)構(gòu)。例如,有時(shí)我們會(huì)將物品捆綁在一起打折出售:
abstract class Item
case class Article(description: String, price: Double) extends Item
case class Bundle(description: String,discount: Double,items: Item*) extends Item
因?yàn)椴挥檬褂胣ew,所以我們可以很容易地給出嵌套對(duì)象定義:
Bundle("Father",20.0,
Article("Scala for Impatient",39.95),
Budile("Anchor Distillery Sampler",10.0,
Article("Old Potrero Rye Whisky",79.95),
Article("Junipero Gin",32.95)))
模式可以匹配到特定地嵌套,比如:
case Bundle(_,_,Article(descr,_),_*) => ...
上述代碼將descr綁定到Bundle地第一個(gè)Article地描述。
你也可以用@表示法將嵌套地值綁定到變量:
case Bundle(_,_, art @ Article(_,_), rest @ _*) => ....
這樣,art就是Bundle中地第一個(gè)Article,而rest則是剩余地Item地序列。
注意,本例中_*是必須地。以下模式:
case Bundle(_,_, art @ Article(_,_),rest) => ...
將只能匹配到那種只有一個(gè)Article再加上不多不少正好一個(gè)Item地Bundle,而這個(gè)Item將被綁定到rest變量。
以下似乎計(jì)算某Item價(jià)格地函數(shù):
def price(it: Item): Double = it match {
case Article(_,p) => p
case Bundle(_,disc, its @ _*) => its.map(price _).sum * disc
}
14.13 樣例類是evil的嗎
樣例類適用于那種標(biāo)記不會(huì)改變的結(jié)構(gòu)。舉例來(lái)說(shuō),Scala的List就是用樣例類來(lái)實(shí)現(xiàn)的。簡(jiǎn)化一些細(xì)節(jié),列表從本質(zhì)上說(shuō)就是:
abstract class List
case object Nil extends List
case class ::(head: Any, tail: List) extends List
列表要么是空,要么是一頭一尾。沒(méi)人會(huì)增加出一個(gè)新的樣例。(下一節(jié)會(huì)看到如何組織別人這樣做)
優(yōu)勢(shì):
模式匹配通常比繼承更容易把我們引向更精簡(jiǎn)的代碼。構(gòu)造時(shí)不需要用new的復(fù)合對(duì)象更加易讀你將免費(fèi)得到toString,equals,hashCode和copy方法
對(duì)于某種特定種類的類,樣例類會(huì)提供給你的是完全正確的語(yǔ)義。有人將他們稱為值類。例如Currency類:
case class Currency(value: Double, unit: String)
一個(gè)Currency(10,“EUR”)和Current(10,“EUR”)是等效的。這樣的類通常是不可變的。
可變字段的樣例類,我們應(yīng)該總是從那些不會(huì)被改變的字段來(lái)計(jì)算和得出其哈希碼。
14.14 密封類
讓編譯器幫你確保你已經(jīng)列出了所有可能的選擇。要達(dá)到這個(gè)目的,你需要將樣例類的通用超類聲明為sealed:
sealed abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
密封類的所有子類都必須再與該密封類相同的文件中定義。舉例來(lái)說(shuō),如果有人想要為歐元添加另一個(gè)樣例類:
case class Euro(value: Double) extends Amount
他們必須在Amount被聲明的那個(gè)文件中完成。
如果某個(gè)類是密封的,那么在編譯期所有子類就是可知的,因而編譯器可以檢查模式語(yǔ)句的完整性。讓所有(同一組)樣例類都擴(kuò)展某個(gè)密封的類或特質(zhì)是個(gè)好的做法。
14.15 模擬枚舉
樣例類讓你可以在Scala中模擬出枚舉類型。
sealed abstract class TrafficLightColor
case object Red extends TrafficLightColor
case object Yellow extends TrafficLightColor
case object Green extends TrafficLightColor
color match {
case Red => "stop"
case Yello => "hurry up"
case Green => "Go"
}
如果覺(jué)得這樣實(shí)現(xiàn)有點(diǎn)笨重,可以使用我們?cè)诘?章介紹過(guò)的Enumeration助手工具。
但這樣可以讓編譯確認(rèn)所有枚舉值被列出。
14.16 Option類型
標(biāo)準(zhǔn)類庫(kù)中的Option類型用樣例類來(lái)表示那種可能存在、也可能不存在的值。樣例子類Some包裝了某個(gè)值,例如:Some(“Fred”)。而樣例對(duì)象None表示沒(méi)有值。
這比使用空字符串的意圖更加清晰,比使用null來(lái)表示缺少某值的做法更加安全。
Option支持泛型。舉例來(lái)說(shuō)Some("Fred")的類型是Option[String]
Map類的get方法返回一個(gè)Option。如果對(duì)于給定的鍵沒(méi)有對(duì)應(yīng)的值,則get返回None。如果有值,就會(huì)將該值包在Some中返回。
你可以用模式匹配來(lái)分析這樣的一個(gè)值:
scores.get("Alice") match {
case Some(score) => println(score)
case None => println("No Score")
}
不過(guò)這比較繁瑣??梢允褂胕sEmpty和get:
val alicesScore = scores.get("ALICE")
if (alicesSocre.isEmpty) println("No score")
else println(alicesScore.get)
這也很繁瑣。可以使用:
println(alicesScore.getOrElse("No socre"))
其實(shí),Map類也提供了getOrElse方法:
println(socres.getOrElse("Alice","No Score"))
如果你想要忽略None值,可以用for推導(dǎo)式:
for(score <- scores.get("Alice")) println(score)
如果get方法返回None,什么也不發(fā)生。如果返回Some,則score將被綁定到它的內(nèi)容。
你也可以將Option當(dāng)作是一個(gè)要么是空,要么帶有單個(gè)元素的集合,并使用諸如map,foreach或filter等方法。例如:
scores.get("Alice").foreach(println _)
上述代碼將打印分?jǐn)?shù),或者如果get返回None的話,什么也不做。
14.17 偏函數(shù)
第15章 注解
15.1 什么是注解
注解語(yǔ)法和Java一樣,例如:
@Test(timeout = 100) def testSomeFeature() {...}
@Entity class Credentials {
@Id @BeanProperty var username: String _
@BeanProperty var password: String _
}
你可以對(duì)Scala使用Java注解,上述的示例中注解(除了@BeanProperty)來(lái)自JUnit和JPA,而這兩個(gè)Java框架并不知道我們用的是Scala
也keyi使用Scala注解。這些注解是Scala特有的,通常由Scala編譯器或編譯器插件處理。
Java注解不影響編譯器的編譯。而Scala中,注解可以影響編譯過(guò)程。舉例來(lái)說(shuō),第5章中@BeanProperty注解將觸發(fā)get和set方法的生成。
15.2 什么可以被注解
在Scala中,你可以為類,方法,字段,局部變量和參數(shù)添加注解,就和Java一樣。
也可以同時(shí)添加多個(gè)注解。先后次序沒(méi)有影響。
在給主構(gòu)造器添加注解時(shí),你需要將注解放置在構(gòu)造器之前,并加上一對(duì)圓括號(hào)(如果注解不帶參數(shù)的話)。
class Credentials @Inject()(var username: String, var password: String)
你還可以為表達(dá)式添加注解。你需要在表達(dá)式后加上冒號(hào),然后是注解本事:
(myMap.get(key): @unchecked) match {...}
可以為類型參數(shù)添加注解:
class MyContainer[@specialized T]
針對(duì)實(shí)際類型的注解應(yīng)放置在類型名稱之后,就像這樣:
String @cps[Unit] // @cps帶一個(gè)類型參數(shù) 第22章
15.3 注解參數(shù)
Java注解可以有帶名參數(shù),比如:
@Test(timeout = 100,expected = classOf(IOException))
不過(guò),如果參數(shù)名為value,則該名稱可以直接忽略。例如:
@Named("creds") var credentials: Crendentials = _
// value參數(shù)的值為"creds"
如果注解不帶參數(shù),則圓括號(hào)可以省去:
@Entity class Crendentials
大多數(shù)注解參數(shù)都有缺省值。舉例來(lái)說(shuō),JUnit的@Test注解的timeout參數(shù)有一個(gè)缺省的值為0,表示沒(méi)有超時(shí)。而expected參數(shù)有一個(gè)假的缺省類來(lái)表示不預(yù)期的任何異常。
如果使用:
@Test def testSomeFeature() {...}
這個(gè)注解等同于:
@Test(timeout = 0, expected = classOf[org.junit.Test.None])
def testSomeFeature() {...}
Java注解的參數(shù)類型只能是:
數(shù)值型的字面量字符串類字面量Java枚舉其他注解上述類型的數(shù)組(但不能是數(shù)組的數(shù)組)
Scala注解的參數(shù)可以是任何類型,但只有少數(shù)幾個(gè)Scala注解利用了這個(gè)增加處理的靈活性。舉例來(lái)說(shuō),@deprecatedName注解有一個(gè)類型為Symbol的參數(shù)。
15.4 注解實(shí)現(xiàn)
明白注解類是如何實(shí)現(xiàn)的。
注解必須擴(kuò)展自Annotation特質(zhì)。例如,unchecked注解定義如下:
class unchecked extends annotation.Annotation
注解類可以選擇擴(kuò)展自StaticAnnotation或ClassfileAnnotation特質(zhì)。StaticAnnotation在編譯單元中可見(jiàn)——它將放置Scala特有的元數(shù)據(jù)到類文件中,而ClassfileAnnotaion的本意是在類文件中生成Java注解元數(shù)據(jù)。不過(guò),Scala 2.9并未支持該功能。
Scala字段定義可能會(huì)引出多個(gè)Java特性,而他們都有可能被添加注解。舉例來(lái)說(shuō),有如下定義:
class Credentials(@NotNull @BeanProperty var username: String)
在這里,總共有六個(gè)可以被注解的目標(biāo):
構(gòu)造器參數(shù)私有的示例字段取值器方法username改值器方法username_=bean取值器getUsernamebean改值器setUsername
默認(rèn)情況下,構(gòu)造器參數(shù)注解僅會(huì)被應(yīng)用到參數(shù)自身,而字段注解只能應(yīng)用到字段。元注解@param,@field,@getter,@setter,@beanGetter和@beanSetter將使得注解被附在別處。舉例來(lái)說(shuō),deprecated注解的定義如下:
@getter @setter @beanGetter @beanSetter
class deprecated(message:String = "",since: String = "")
extends annotation.StaticAnnotation
你也可以根據(jù)臨時(shí)需要應(yīng)用這些元注解:
@Entity class Credentials {
@(Id @beanGetter) @BeanProperty var id = 0
...
}
在這種情況下,@Id注解將被應(yīng)用到Java的getId方法,這是JPA要求的方法,用來(lái)訪問(wèn)屬性字段。
15.5 針對(duì)Java特性的注解
15.6 用于優(yōu)化的注解
15.7 用于錯(cuò)誤和警告的注解
如果你給某個(gè)特性加上了**@deprecated**注解,則每當(dāng)編譯器遇到對(duì)這個(gè)特性的使用時(shí)都會(huì)生成一個(gè)警告信息。該注解有兩個(gè)選填參數(shù),message和since。
@deprecated(message = "Use factorial(n: BigInt) instead")
def factorial(n: Int): Int = ...
@deprecatedName可以被應(yīng)用到參數(shù)上,并給出一個(gè)該參數(shù)之前使用過(guò)的名稱。
def draw(@deprecatedName('sz) size: Int,style: Int = NORMAL)
仍然可以調(diào)用draw(sz=12),不過(guò)你將會(huì)得到一個(gè)表示該名稱已過(guò)時(shí)的警告。
以單引號(hào)開(kāi)頭的名稱。
@implicitNotFound注解用于在某個(gè)隱式參數(shù)不存在的時(shí)候生成有意義的錯(cuò)誤提示。見(jiàn)21章。
@unchecked注解用于在匹配不完整時(shí)取消警告信息。舉例來(lái)說(shuō),假定我們知道某個(gè)列表不可能為空:
(lst: @unchecked) match {
case head :: tail => ...
}
編譯器不會(huì)報(bào)告說(shuō)沒(méi)給Nil選項(xiàng)。如果lst是Nil,則運(yùn)行期會(huì)報(bào)出異常。
@uncheckedVariance注解會(huì)取消與型變相關(guān)的錯(cuò)誤提示。舉例來(lái)說(shuō),java.util.Comparator按理應(yīng)該是逆變的。如果Student是Person的子類型,那么在需要Comparator[Student]的時(shí)候,我們也可以使用Comparator[Person]。但是,Java的泛型不支持型變。我們可以通過(guò)@uncheckedVariance注解來(lái)解決這個(gè)問(wèn)題:
trait Comparator[-T] extends
java.lang.Comparator[T @uncheckedVariance]
17 類型參數(shù)
17.1 泛型類
class Pair[T,S](val first: T,val seconds: S)
scala會(huì)從構(gòu)造參數(shù)推斷出實(shí)際類型
val p = new Pair(42,"String")
也可以自己指定類型:
val p2 = new Pair[Any,Any](42,"String")
17.2 泛型函數(shù)
def getMiddle[T](a: Array[T]) = a(a.length / 2)
你需要把類型參數(shù)放在方法名之后。
scala會(huì)從調(diào)用該方法使用的實(shí)際參數(shù)來(lái)推斷類型。
getMiddle(Array("Mary","a"))
如果有必要,你可以指定類型:
val f = getMiddle[String] _ // 這是具體的函數(shù),保存到f
17.3 類型變量界定
對(duì)類型進(jìn)行界定。添加一個(gè)上界T<:Comparable[T]。由于這個(gè)類內(nèi)有些方法不是所有的類適用。
class Pair[T <: Comparable[T]](val first: T, val second: T) {
def samller = if(first.compareTo(second) < 0) first else second
}
這意味著T必須是Comparable[T]的子類型。
添加一個(gè)下界 >:。傳入的類型必須是原類型的超類型。
def replaceFirst[R >: T](newfirst: R) = new Pair[R](newfirst,second)
17.4 視圖界定
允許隱式轉(zhuǎn)換到類型。
class Pair[T <% Comparable[T]]
17.5 上下文界定
視圖界定要求必須存在一個(gè)從T到V的隱式轉(zhuǎn)換。
上下文界定的形式為T:M,其中M是另一個(gè)泛型類。它要求必須存在一個(gè)類型為M[T]的隱式值。
class Pair[T: Ordering](val first: T,val second: T) {
def smaller(implicit ord: Ordering[T]) =
if (ord.compare(first,second) < 0) first else second
}
必須存在一個(gè)類型為Ordering[T]的隱式值。該隱式值可以被用在該類的方法中。
17.6 Manifest上下文界定
實(shí)例化一個(gè)泛型的Array[T],需要一個(gè)Manifest[T]對(duì)象。
如果你要編寫一個(gè)泛型函數(shù)來(lái)構(gòu)造泛型函數(shù)的話,你需要傳入這個(gè)Manifest對(duì)象來(lái)幫忙。由于它是構(gòu)造器的隱式參數(shù),你可以用上下文界定:
def makePair[T : Manifest](first: T, seconde: T) {
val r = new Array[T](2); r(0) = first; r(1) = second; r
}
如果你調(diào)用makePari(4,9),編譯器會(huì)定位到隱式的Manifest[Int]并實(shí)際上調(diào)用makePair(4,9)(intManifest)。這樣一來(lái),該方法調(diào)用的就是new Array(2)(intManifest),返回基本類型的數(shù)組int[2]。
在虛擬機(jī)中,泛型相關(guān)的類型信息是被抹掉的。
17.7多重界定
T >: Lower <: Upper
T <: Comparable[T] with Serializable with Cloneable
T <% Comparable[T] <% String //多個(gè)視圖界定
T : Ordering : Manifest //多個(gè)上下文界定
17.9 型變
class Pair[+T] // 代表與T協(xié)變,也就是說(shuō),它與T按同樣的方向型變。由于Student是PERSON的子類,Pair[Student]也就是Pair[Person]的子類型了。
協(xié)變
trait Friend[-T] {
def beFriend(someone: T)
}
逆變。采用超類型。
21 隱式轉(zhuǎn)換與隱式參數(shù)
隱式對(duì)象是如何被自動(dòng)呼出用于執(zhí)行轉(zhuǎn)換或其他任務(wù)的。
21.1 隱式轉(zhuǎn)換
隱式轉(zhuǎn)換函數(shù):以implicit關(guān)鍵字聲明的帶有單個(gè)參數(shù)的函數(shù)。
這樣的函數(shù)將被自動(dòng)應(yīng)用,將值從一種類型轉(zhuǎn)換為另一個(gè)類型。
implicit def int2Fraction(n: Int) = Fraction(n,1)
//使用
val result = 3 * Fraction(4,5)//將調(diào)用int2Fraction(3)
隱式轉(zhuǎn)換函數(shù)將整數(shù)3轉(zhuǎn)換成了一個(gè)Fraction對(duì)象。這個(gè)對(duì)象接著又被乘以Fraction(4,5)
可以給隱式轉(zhuǎn)換函數(shù)起任何名稱。由于你并不顯式地調(diào)用它。但建議使用source2Target這種命名方式。
21.2 利用隱式轉(zhuǎn)換豐富現(xiàn)有類庫(kù)的功能
用處:當(dāng)希望某個(gè)類有某個(gè)方法,而這個(gè)類的作者沒(méi)有提供的時(shí)候。
class RichFile(val from: File) {
def read = Source.fromFile(from.getPath).mkString
}
implicit def file2RichFile(from: File) = new RichFile(from)
將原來(lái)的類型轉(zhuǎn)換到新的類型,再在新的類型定義想要有的方法。
21.3 引入隱式轉(zhuǎn)換
Scala會(huì)考慮如下的隱式轉(zhuǎn)換函數(shù):
位于源或目標(biāo)類型的伴生對(duì)象中的隱式函數(shù)位于當(dāng)前作用域可以以單個(gè)標(biāo)識(shí)符指代的隱式函數(shù)
比如int2Fraction函數(shù)。我們可以將它放到Fraction伴生對(duì)象中,這樣它就能夠被用來(lái)將整數(shù)轉(zhuǎn)換為分?jǐn)?shù)了。
或者,假定我們把它放到了FractionConversions對(duì)象當(dāng)中,而這個(gè)對(duì)象位于com.horstmann.impatient包。如果你想要使用這個(gè)轉(zhuǎn)換,就需要引入FractionConversions對(duì)象,例如:
import com.horstmann.impatient.FractionConversions._ //一定要有._
int2Fraction方法只能以FractionConversions.int2Fraction的形式被任何想要顯式調(diào)用它的人使用。但如果該函數(shù)不能直接以int2Fraction訪問(wèn)到,不加限定詞的話,編譯器是不會(huì)使用它的。
可以引入局部化以盡量避免不想要的轉(zhuǎn)換發(fā)生:
object Main extends App {
import com.horstmann.impatient.FractionConversions._
xxx
}
如果某個(gè)特定的隱式轉(zhuǎn)換給你帶來(lái)麻煩,你可以將它排除在外:
import com.horstmann.impatient.FractionConversions.{fractions2Double => _,_}
21.4 隱式轉(zhuǎn)換規(guī)則
隱式轉(zhuǎn)換在如下三種各不相同的情況會(huì)被考慮:
當(dāng)表達(dá)式的類型與預(yù)期的類型不同時(shí):sqrt(Fraction(1,4))當(dāng)對(duì)象訪問(wèn)一個(gè)不存在的成員時(shí):new File("a").read當(dāng)對(duì)象調(diào)用某個(gè)方法,而該方法的參數(shù)聲明與傳入?yún)?shù)不匹配時(shí):3 * Fraction
另一方面,有三種情況編譯器不會(huì)嘗試使用隱式轉(zhuǎn)換:
如果代碼可以在不使用隱式轉(zhuǎn)換的前提下通過(guò)編譯,那么就不會(huì)使用隱式轉(zhuǎn)換。編譯器不會(huì)嘗試同時(shí)執(zhí)行多個(gè)轉(zhuǎn)換存在二義性的轉(zhuǎn)換是個(gè)錯(cuò)誤。舉例:convert1(a) * b 和convert2(a) * b
scalac -Xprint:typer MyProg.scala // 顯式隱式轉(zhuǎn)換
21.5 隱式參數(shù)
函數(shù)或方法可以帶有一個(gè)標(biāo)記為implicit的參數(shù)列表。這種情況下,編譯器將會(huì)查找缺省值,提供給該函數(shù)或方法。
def quote(what: String)(implicit delims: Delimiters) = {
delims.left + what + delims.right
}
在這種情況下,編譯器將會(huì)查找一個(gè)類型為Delimiters的隱式值。
這必須是一個(gè)聲明為implicit的值。編譯器將會(huì)在如下兩個(gè)地方查找這樣的一個(gè)對(duì)象:
在當(dāng)前作用域所有可以用單個(gè)標(biāo)識(shí)符指代的滿足類型要求的val和def與所要求類型相關(guān)聯(lián)的類型的伴生對(duì)象。相關(guān)聯(lián)的類型包括所要求類型本身,以及它的類型參數(shù)(如果它是一個(gè)參數(shù)化的類型的話)
比如:
object FrenchPunctuation {
implicit val quoteDelimiters = Delimiters("<<",">>")
}
import FrenchPunctuation._
如此一來(lái),就可以引入隱式值。
對(duì)于給定的數(shù)據(jù)類型,只能有一個(gè)隱式的值。因此,使用常用類型的隱式參數(shù)不是一個(gè)好主意。例如:
def quote(what: String)(implicit left: String, right: String) // 別這樣做
上述代碼行不通,因?yàn)檎{(diào)用者沒(méi)法提供兩個(gè)不同的字符串。
21.6 利用隱式參數(shù)進(jìn)行隱式轉(zhuǎn)換
隱式的函數(shù)參數(shù)可以被用作隱式轉(zhuǎn)換。
def smaller[T](a: T,b: T)(implicit order: T => Ordered[T])
= if (order(a) < b) a else b
由于Ordered[T]特質(zhì)有一個(gè)接受T作為參數(shù)的<操作符。因此這個(gè)版本是正確的。
order是帶有單個(gè)參數(shù)的函數(shù),被打上了implicit標(biāo)簽,并且有一個(gè)以的那個(gè)標(biāo)識(shí)符出現(xiàn)的名稱。因此,它不僅是一個(gè)隱式參數(shù),它還是一個(gè)隱式 轉(zhuǎn)換。正因?yàn)槿绱?,函?shù)體可以略去對(duì)order的顯式調(diào)用:
def smaller[T](a: T,b: T)(implicit order: T=> Ordered[T])
= if (a
如果你想要調(diào)用:
smaller(Fraction(1,2),Fraction(2,1))
就需要定義一個(gè)Fraction=> Ordered[Fration]的函數(shù),要么在調(diào)用的時(shí)候顯式寫出,或者定義為一個(gè)implicit val。
第18章 高級(jí)類型
18.1 單例類型
給定任何引用v,你可以得到類型v.type,它有兩個(gè)可能的值:v和null。
我們來(lái)看那種返回this的方法,通過(guò)這種方法你可以把方法調(diào)用串接起來(lái):
class Documnet {
def setTitle(title: String) = {..;this}
def setAuthor(author: String) = {...; this}
}
這樣的話,你就可以編寫如下的代碼:
article.setTitle("..").setAuthor("cay")
但是,如果你還有子類的話,那么問(wèn)題來(lái)了:
class Book extends Document {
def addChapter(chapter: String) = {...; this}
}
val book = new Book()
book.setTitle("xx").addChapter(chapter1) // 錯(cuò)誤
由于setTitle返回的是this,scala將返回類型推斷為Document。但Documnet并沒(méi)有addChapter方法
解決方法是聲明setTitle的返回類型為this.type:
def setTitle(title: String): this.type = {...; this}
如果你想要定義一個(gè)接受object實(shí)例作為參數(shù)的方法,你也可以使用單例類型。可能會(huì)問(wèn):你什么時(shí)候才會(huì)這樣做呢,畢竟如果只有一個(gè)實(shí)例,方法直接用它就好了,為什么還要傳入呢?有些人喜歡構(gòu)造那種讀起來(lái)像英文的流利接口。
book set Title to "Scala"
object Title
class Document {
private var useNextArgAs: Any = null
def set(obj: Title.type): this.type = {useNextArgAs = obj; this }
def to(arg: String) = if (useNextArgAs == Title) title = arg; else ...
}
注意Title.type參數(shù)。
18.2 類型投影
嵌套類從屬于包含它的外部對(duì)象。
import scala.collection.mutable.ArrayBuffer
class Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Member]
}
private val members = new ArrayBuffer[Member]
def join(name: String) = {
val m = new Member(name)
members += m
m
}
}
每個(gè)網(wǎng)絡(luò)實(shí)例都有它自己的member類。舉例:
val chatter = new Network
val myFace = new Network
//現(xiàn)在chatter.Member和myFace.Member是不同的類
你不能將其中一個(gè)網(wǎng)絡(luò)的成員添加到另一個(gè)網(wǎng)絡(luò)。
如果你不希望有這樣的約束,你應(yīng)該把Member類直接挪到Network類之外。一個(gè)好的地方可能是Network的伴生對(duì)象中。
如果你要的就是細(xì)粒度的類,只是偶爾想使用更為松散的定義,那么可以用類型投影Network#Member,意思是任何Network的Member。
class Network {
class Member(val name: String) {
val contracts = new ArrayBuffer[Network#Member]
}
}
18.4 類型別名
對(duì)于復(fù)雜的類型,你可以用type關(guān)鍵字創(chuàng)建一個(gè)簡(jiǎn)單的別名,就像這樣:
class Book {
import scala.collection.mutable._
type Index = HashMap[String,(Int,Int)]
}
類型別名必須被嵌套在類或?qū)ο笾?。它不能出現(xiàn)在scala文件的頂層。
18.5 結(jié)構(gòu)類型
指的是一組關(guān)于抽象方法、字段和類型的規(guī)格說(shuō)明,這些抽象方法、字段和類型是滿足該規(guī)則的類型必須具備的。
舉例來(lái)說(shuō):如下方法帶有一個(gè)結(jié)構(gòu)類型參數(shù)target:
def appendLines(target: {def append(str: String): Any},lines: Iterable[String]) {
for()...
}
你可以對(duì)任何具備append方法的類的實(shí)例調(diào)用appendLines方法。這比定義一個(gè)Appendable特質(zhì)更為靈活,因?yàn)槟憧赡懿⒉豢偸悄軌驅(qū)⒃撎刭|(zhì)添加到使用的類上。
在幕后,scala使用反射來(lái)調(diào)用target.append(..)。結(jié)構(gòu)類型讓你可以安全而方便地做這樣的反射調(diào)用。
不過(guò),相比常規(guī)方法調(diào)用,反射方法調(diào)用的開(kāi)銷大得多。因此,你應(yīng)該只在需要抓住那些無(wú)法共享一個(gè)特質(zhì)的類得到共通行為的時(shí)候才使用結(jié)構(gòu)類型。
柚子快報(bào)邀請(qǐng)碼778899分享:快學(xué)Scala
好文閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。