欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

目錄

柚子快報(bào)邀請(qǐng)碼778899分享:快學(xué)Scala

柚子快報(bào)邀請(qǐng)碼778899分享:快學(xué)Scala

http://yzkb.51969.com/

第一章

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 輸入和輸出

print

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

http://yzkb.51969.com/

好文閱讀

評(píng)論可見(jiàn),查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://m.gantiao.com.cn/post/19173855.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問(wèn)

文章目錄