操作符重載?
- 舉個例子[P60]
-
1 + 2
中的+
操作符是一個方法。
-
- 首先,在 Scala 中,萬物皆對象,包括基本數據類型。其次,使用中綴表達式法表示單參數方法時,其中的點號和括號可以省略,所以:
1 + 2
等價于1.+(2)
。[P60] - 調用無參方法也可以省略點號,這種寫法也被稱為后綴表示法。由于有的時候后綴表示法會觸發歧義,所以 Scala 2.10+ 將其作為可選特性,需要
import scala.language.postfixOps
,或者使用scala -language:postfixOps
開啟 REPL 來開啟這個特性。[P60 - 61] - 使用
scala -feature
開啟 REPL 可以獲取更多有意義的警告信息。[P61] - 允許出現在標示符中的字符:[P61 - 62]
[略]- Scala 沒有自增 / 自減運算符。
- 在模式匹配表達式中,以小寫字母開頭的標記會被解析為變量標示符,而大寫字母開頭的標記則會被解析為常量標示符(如類名)。
無參數方法
-
Scala 允許用戶決定是否為無參數方法使用括號:[P63]
- 在定義無參數方法時如果省略括號,那么調用該方法的時候必須省略括號。但是如果定義時沒有省略括號,用戶在調用時可以選擇省略或不省略。
-
Scala 社區的習慣:[P63]
- 定義無副作用的無參方法時省略括號,有副作用的則添加括號。
在
scala
或者scalac
中添加-Xlint
參數時,定義有副作用的無參方法,省略括號時系統會發出警告。-
一個經典的省略括號的例子(方法鏈):[P63 - 64]
def isEven(n: Int) = (n % 2) == 0 List(1, 2, 3, 4) filter isEven foreach println // 展開(提示:關注一下每一個方法需要的參數,這里邊并沒有省略任何變量參數) List(1, 2, 3, 4).filter((i: Int) => isEven(i)).foreach((i: Int) => println(i)) List(1, 2, 3, 4).filter(i => isEven(i)).foreach((i: Int) => println(i)) List(1, 2, 3, 4).filter(isEven).foreach(println) List(1, 2, 3, 4) filter isEven foreach println
簡單梳理一下:
filter
接收一個函數參數isEven
,并返回一個新的List
,然后調用foreach
,它也接收一個函數參數,這個函數正好是println
。其實參數一點都沒省略,就是單純的省略括號和省略.
運算符。假如方法鏈中的某一個方法接收 0 個或大于 1 個的參數,需要為部分或全部方法補上點號。
優先級規則
-
優先級表 [P64]
- 所有字母
- |
- ^
- &
- < >
- = !
- :
- / %
- 其他特殊字符
當 = 用于賦值時,優先級最低。[P64]
在 Scala 中,任何名字以
:
結尾的方法都與右邊的對象所綁定,其他的方法則是左綁定的。[P65]-
舉個例子:cons 操作:使用
::
方法將某一元素放置到列表前邊。[P65]val list = List('b', 'c', 'd') val nList = 'a'::list
領域特定語言
[P65 - 66][略]
Scala 的 if
語句
Scala 的
if
語句是具有返回值的表達式。[P66]-
if
語句的返回值類型也被稱為所有條件分支的最小上界,也就是與每條each
子句可能返回值類型最接近的父類型。以下例子中返回String
:[P66]val configFile = new java.io.File("somefile.txt") val configFilePath = if (configFile.exists()) { configFile.getAbsolutePath() // String } else { configFile.createNewFile() configFile.getAbsolutePath() // String }
Scala 沒有三元表達式。[P66]
Scala 中的 for
推導式
生成器表達式
-
<-
[?P67]
保護式:篩選元素
-
通過加入
if
表達式來篩選元素,這些表達式也被稱為保護式。[P68]val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier") for (breed <- dogBreeds if breed.contains("Terrier") ) println(breed)
val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier") for (breed <- dogBreeds if breed.contains("Terrier") if !breed.startsWith("Yorkshire") ) println(breed) // 等價于 for (breed <- dogBreeds if breed.contains("Terrier") && !breed.startsWith("Yorkshire") ) println(breed)
Yielding
-
使用
yield
關鍵字在for
表達式中生成新的集合,使用大括號替代圓括號,以相似的方法把參數列表封裝在大括號中:[P68]val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier") val filteredDogBreeds = for { breed <- dogBreeds if breed.contains("Terrier) && !breed.startsWith("Yorkshire") } yield breed
當
for
推導式僅包含單一表達式時使用圓括號,當其包含多個表達式時使用大括號。[P69]
擴展作用域與值定義
-
Scala 允許在
for
表達式中的最初部分定義值,并可以在后面的表達式中使用該值。[P69]val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier") for { breed <- dogBreeds upcasedBreed = breed.toUpperCase() // 盡管 upcasedBreed 不可變,但并不需要使用 val 對其進行限定。 } println(upcasedBreed)
-
在
for
表達式中使用Option
:[P69 - 70]val dogBreeds = List(Some("Doberman"), None, Some("Yorkshire Terrier"), Some("Dachshund")) for { breedOption <- dogBreeds // 系統隱式的加上了 if breedOption != None breed <- dogBreeds upcaseBreed = breed.toUpperCase() } println(uppercaseBreed) for { SOme(breed) <- dogBreeds upcasedBreed = breed.toUpperCase() } println(upcasedBreed)
當遍歷某一集合或其他像
Option
這樣的容器并試圖提取值時,應該使用箭頭。當執行并不需要迭代的賦值操作時,應該使用等于號。[P70]
使用 try
、catch
和 final
語句
Scala 不支持已被視為失敗設計的檢查型異常( checked exception )。Scala 將 Java 中檢查異型異常視為非檢查型,而且方法聲明中也不包含
throw
子句。[P72]Scala 提供了有助于 Java 互操作的
@throws
注解。[P72]??-
在 Scala 中,異常處理用模式匹配來處理:[P72 - 73]
var source: Option[Source] = None try { source = Some(Source.fromFile(fileName)) val size = source.get.getLines.size println(s"file $fileName has $size lines") } catch { case NonFatal(ex) => println(s"Non fatal exception! $ex)" // 捕獲 *非致命* 錯誤。 } finally { for (s <- source) { println(s"Closing $fileName...") s.close } }
在 SBT 中,使用
run-main
啟動程序。啟動程序前允許傳遞參數。[P74]Scala 允許自定義異常:
throw new MyBadException(...)
。如果自定義異常是一個 case 類,那么拋出異常時可以省略new
關鍵字。[P74]自動資源管理:ScalaARM
名字調用和值調用 [P75 - 78]
-
manage.apply
方法def apply[ R <: { def close():Unit }, // 1 T] (resource: => R) // 2 (f: R => T) = {...} // 3
-
<:
表明R
屬于其他類的子類。 在本例中,R
的父類型是一個包含close():Unit
方法的結構類型。- 結構化類型允許我們使用反射機制嵌入包含
close():Unit
方法的任意類型,但會造成許多系統開銷,結構化類型也的代價也十分昂貴,所以 Scala 將反射列為可選特性。
- 結構化類型允許我們使用反射機制嵌入包含
- 傳名參數:在進入
manage.apply
之前,該表達式都不會被執行,直到在apply
中調用這個參數。(延遲計算) - 匿名函數,輸入為
resource
返回值類型為T
。
-
傳名參數的行為與函數類似,每次使用該參數時便會執行表達式。[P77]
-
運用傳名參數,以遞歸取代循環結構:[P77]
@annotation.tailrec def continue(conditional: => Boolean)(body: => Unit) { if (conditional) { body continue(conditional)(body) } } var count = 0 continue(count < 5) { println(s"at $count") count += 1 }
傳名參數會在每次被引用時估值。它的求值會被推遲,并可能一再地被重復調用,因此此類參數具有惰性。除此之外,Scala 還提供了惰性值。[P78]
惰性賦值
-
以延遲的方式初始化某個值,并且表達式不會被重復計算。常見場景:[P78]
- 表達式執行代價昂貴。
- 縮短模塊的啟動時間,將不需要的工作推遲。
- 確保對象中的其他字段的初始化過程優先執行。
-
舉個例子:[P78]
object ExpensiveResource { lazy val resource: Int = init() // 推遲到需要時計算。 def init(): Int = { // Some Expensive Operations. 0 } {
Scala 通過保護式( guard )來實現惰性值,由于無法解除保護式,所以會造成額外的開銷。只有當保護式帶來的額外開銷小于初始化帶來的開銷,或者某些值惰性化能簡化系統初始化過程,并確保執行順序滿足依賴條件時才應該使用惰性值。[P78]
枚舉
Scala
定義了Enumration
類來支持枚舉,意味著 Scala 不為枚舉提供特殊語法。[P79]-
一個例子:[P79 - 80]
object Breed extends Enumeration { type Breed = Value val doberman = Value("Doberman Pinscher") val yorkie = Value("Yorkshire Terrier") } // 在其他類中 for(breed <- Breed.values) println(s"${breed.id}, $breed")
使用
.values
取得枚舉列表。Value
對象會自動從 0 開始逐一遞增分配 ID 。-
在上述定義中,
Breed
是Value
類型的一個別名,目的在于:def isTerrier(b: Breed) = b.toString.endsWith("Terrier") Breed.values filter isTerrier foreach println
如果沒有類型別名,上邊的代碼會出現語法錯誤。
-
有時不希望給枚舉值命名:[P80]
object WeekDay extends Enumeration { type WeekDay = Value val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value } def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun) WeekDay.values filter isWorkingDay foreach println
case 類相比枚舉值有兩大有點:允許添加方法和字段;適用于包含未知枚舉值的場景。[P81]
可插入字符串
- 在使用
printf
格式字符串時(不是printf
函數),Scala 會調用 Java 的Formatter
類。printf
格式指令與${...}
之間不能有空格。[P81] - 在格式字符串中,將
Double
用Int
格式化輸出會引發編譯錯誤。[P82] - Scala 編譯器會在某些語境中對 Java 字符串進行封裝并提供一些額外的方法,這些定義在
scala.collection.immutable.StringLike
中。[P82] - “原生”( raw )插入器不會對控制字符串進行擴展。[P82]
- Scala 允許自定義字符串插入器。[P83]
Trait
:Scala 語言的接口和“混入”
Scala 使用
trait
來替代接口。(一種簡單的理解是:允許將聲明方法實現的接口)[P83]trait
支持“混入”,即不只是定義方法和字段。可以參考書上關于日志系統的代碼。[P84]使用
with
關鍵字混入trait
。Scala 允許混入多個trait
,混入的trait
中的方法彼此獨立。[P84 - 85]-
子類向父類傳參的方式:
class LoggedServiceImportante(name: String) extends ServiceImportante(name) with StdoutLogging {...} // 子類將 name 傳遞給父類
Scala 要求所有方法覆寫都要使用
override
關鍵字。與 Java 一樣,Scala 使用super.methodName
訪問父類方法。