分號
- 在 scala REPL 中,使用
:paste
模式輸入多行代碼,然后用 Ctrl-D 結束。[P29]
變量聲明
-
val
和var
關鍵字只標識引用本身是否可以指向另一個不同的對象,它們并未表明其所引用的對象是否可變。[P30] - 為了減少可變性引起的 bug ,應當*盡可能地使用不可變變量。[p30]
Range [P31]
-
1 to 10
: 1, 2, .., 10 -
1 until 10
: 1, 2, 3, ..., 9 -
1 to 10 by 3
: 1, 4, 7, 10 -
1 to 10 by -3
: 10, 7, 4, 1 1L to 10L by 3L
1f to 10.3f by 0.3f
'a' to 'g' by 3
偏函數
”偏“:偏函數不處理所有可能的輸入,只處理能與至少一個
case
語句匹配的輸入。[P32]在偏函數中只能用
case
語句,而整個函數必須用花括號包圍。[P32]如果偏函數被調用,而函數的輸入卻與所有語句都不匹配,系統會拋出一個
MatchError
運行時錯誤。[P32]使用
isDefinedAt
方法測試特定輸入是否與偏函數匹配:f.isDefinedAt(x)
返回true / false
。[P32]-
可以使用
orElse
語法連接偏函數:[P32]val pf1 = PartialFunction[Any, String] = {case s:String => "YES"} val pf2 = PartialFunction[Any, String] = {case d:Double => "YES"} val pf = pf1 orElse pf2
方法聲明
-
copy
方法也是 case 類自動創建的。它允許你在創建 case 類的新實例時,只給出與原始對象不同部分的參數。[P33]
方法具有多個參數列表
-
Scala 允許我們把參數列表兩邊的圓括號替換為花括號。[P34]
def draw(offset: Point = Point(0.0, 0.0))(f: String => Unit): Unit = f(s"draw(offset = $offset), ${this.toString}")
s.draw(Point(1.0, 2.0))(str => println(s"ShapesDrawingActor: $str")) // 等價于 s.draw(Point(1.0, 2.0)){ str => println(s"ShapesDrawingActor: $str")) }
使用具有多個參數列表的方法有助于 Scala 進行參數推斷。[?P35]
使用具有多個參數列表的方法中,可以使用最后一個參數列表推斷隱含參數。?[P35]
隱含參數是用
implicit
關鍵字聲明的參數,當相應方法被調用時,我們可以顯式指定這個參數,或者不指定,讓編譯器在當前作用域找到一個合適的值作為參數。[P35]隱含參數可以代替參數默認值,而且更加靈活。[P35]
Future 簡介
scala.concurrent.Future
? 是 Scala 提供的一個并發工具,其 API 使用隱含參數減少代碼冗余。[P35]-
Future 與隱含參數[P36]
apply[T](body: => T)(implicit executor: ExecutionContext): Future[T]
import scala.concurrent.ExecutionContext.Implicits.global val future = { ... }
只有用
implicit
關鍵字聲明,在當前作用域可見的對象才能用作隱含值;只有被聲明為implicit
的函數參數才允許調用時不給出實參,而采用隱含的值。??[P37]
嵌套方法的定義與遞歸
內部變量會屏蔽外部相同名稱的變量。[P38]
Scala 采用局部作用域類型推斷,所以必須為遞歸方法顯式聲明返回值類型。[P39]
尾遞歸:調用遞歸函數是該函數的最后一個表達式,該表達式的返回值就是所調用的遞歸函數的返回值。[P39]
-
尾遞歸檢查
@tailrec
[P39]import scala.annotation.tailrec def factorial(i: Int): Long = { @tailrec def fact(i: Int): Long = { if (i <= 1) accumulator else fact(i - 1, i * accumulator) } fact(i, 1) }
- 如果函數不是尾遞歸,編譯器會報錯。
外層方法中的一切在嵌套方法中都是可見的,包括傳遞給外層方法的參數。
推斷類型信息
-
什么時候需要顯式類型注解:[P41]
- 聲明了可變的
var
變量或不可變的val
變量,沒有進行初始化。 - 所有的方法參數(如
def deposit(amount: Money) = {...}
)。 - 方法的返回值類型,在以下情況中必須顯式聲明其類型。
- 在方法中明顯地使用了
return
(即使在方法末尾也是如此)。 - 遞歸方法。
- 兩個或多個方法重載(擁有相同的函數名),其中一個方法調用了另一個重載方法,調用者需要顯式類型注解。
- Scala 推斷出的類型比你期望的類型更為寬泛,如
Any
。(少見)
- 在方法中明顯地使用了
- 聲明了可變的
-
Scala 支持可變方法,但是:[P42]
- 方法可以用有其他參數,但必須位于可變參數之前;
- 方法只能有一個可變參數。
加入返回類型注解可以在編譯階段發現推斷類型錯誤,也有助于提升代碼可讀性。[P43]
Scala 將方法體前的聲明和等號當做函數定義,而在函數式編程中,函數總要有返回值。當 Scala 發現函數體之前沒有等號時,就認定程序員希望該方法是一個 procedure ,意味著它只返回
Unit
。[P44]Unit
類型擁有一個名為()
的值。[P44]
保留字 [P44 - 45]
forSome
:用在已經存在的類型聲明中,限制其能夠使用的具體類型。implicit
:使得方法或變量值可以被???????用于隱含轉換;將方法參數標記為可選的,只要在調用該方法時,作用域內有類型匹配的候選對象,就會使用該對象作為參數。lazy
:推遲val
變量的賦值。requires
:[已停用]sealed
:用于父類型,要求所有派生的子類必須在同一個源文件中聲明。trait
:這是一個混入模塊,對類的實例添加額外的狀態和行為;也可以用于聲明而不實現方法,類似 Java 的interface
。type
:聲明類型。yield
:在for
循環中返回元素,這些元素會構成一個序列。還有一些字符,具體看文檔。
Scala 沒有
break
和continue
。-
在 Scala 中引用保留的 Java 方法時,要為其加上
``
。java.util.Scanner.`match`
字面量
整數字面量
- 整數字面量如果超過規定的范圍,會引發一個編譯錯誤。[P46]
字符字面量
- 不可打印的 Unicode 字符(如:\u0009 水平制表符)在 Scala 中是不允許的。[P48]
字符串字面量
- 字符串字面量是被雙引號或者三重引號包圍的字符串序列,如
"""..."""
。[P48] - 用三重雙引號包含的字符串字面量被稱為多行字符串字面量,這些字符串可以跨越多行, 換行符是字符串的一部分。可以包含任意字符,但不能出現三個連續的雙引號。三重雙引號包含的字符串不轉義。[P49]
- 在多行字符串中,可以使用
String.stripMargin
移除每行字符串開頭的空格和第一個遇到的垂直分割符|
。 [P49]- 如果希望用別的字符替代
|
,可以使用stripMargin
的重載版本,該函數可以指定一個Char
參數替代|
。如果想要移除整個字符串(而不是字符串的各個行)的前綴和后綴,有相應的stripPrefix
和stripSuffix
方法可以完成。
- 如果希望用別的字符替代
符號字面量
- Scala 支持符號。符號是一些規定的字符串。兩個同名符號會指向內存中的同一對象。[P50]
- 符號是單引號( ' )后邊跟上一個或多個數字、字幕或下劃線,但第一個字符不能是數字。[P50]
- 符號字面量
'id
是表達式scala.Symbol("id")
的簡寫形式,如果要創建一個包含空格的符號,可以使用Symbol.apply
,比如Symbol(" programming Scala")
。[P50]
函數字面量
-
(i: Int, s: String) => s + i
是一個類型為Function2[Int, String, String]
的函數字面量。以下聲明等價:[P50]val f1: (Int, String) => String = (i, s) => s + i val f2: Function2[Int, String, String] = (i, s) => s + i
元組字面量
Scala 庫中包含
TupleN
類,用于組建 N 元素組,它以小括號加上逗號分隔的元素序列的形式來創建元素組。TupleN
表示的多個類各自獨立,N 的取值從 1 到 22 ,包括 22 。[P50]-
用字面量語法聲明
Tuple
類型的變量:[P50]val t1: (Int, String) = (1, "two") val t2: Tuple2[Int, String] = (1, "two")
-
使用元組:[P51]
val t = ("Hello", 1, 2.3) println("Print the whole tuple: " + t) println("Print the first item: " + t._1) println("Print the second item: " + t._2) println("Print the third item: " + t._3)
表達式
t._n
提取元組 t 中的第 n 個元素。元組從 1 開始計數。[P51]-
一個量元素的元組,有時被簡稱為 pair ,有很多定義 pair 的方法,除了在圓括號中列出元素值以外,還可以”箭頭操作符“放在兩個值之間,也可以用相應類的工廠方法:[P51]
(1, "one") 1 -> "one Tuple2(1, "one")
箭頭操作符只適用于兩元素的元組。
Option
、 Some
和 None
:避免使用 null
抽象類
Option
具有兩個兩個具體的子類Some
和None
。Some
用于表示有值,None
用于表示沒有值。[P52]-
舉個例子:[P52]
val stateCapitals = Map( "Alabama" -> "Montgomery", "Alaska" -> "Juneau" ) stateCapitals.get("Alabama") // Some(Montgomery) stateCapitals.get("Alabama").get // "Montgomery" stateCapitals.get("Unknown") // None stateCapitals.get("Unknown").getOrElse("Oops") // "Oops"
Map.get
方法返回Option[T]
,本例子中T
為String
。
封閉類的繼承
-
關鍵字
sealed
用于實現封閉類的繼承。[P53]sealed abstract class Option[+A] ... { ... }
關鍵字
sealed
告訴編譯器,所有的子類必須在同一個源文件中聲明。 如果為了防止用戶派生任何子類,也可以用
final
。
用文件和命名空間組織代碼
-
Scala 支持嵌套
package
語法:[P54 - 55]package org { ... package scala { ... package demo { ... } } } package com { package example { ... } } package com1.example2 { ... }
-
使用連續包聲明時,必須使用單獨的
package
語句:[P55]package com.example // 導入 example 中所有包級別的聲明 package mypkg // 導入 mypkg 中所有包級別的聲明 class MyPkgClass { ... }
Scala 不允許在腳本中定義包,腳本被隱含包裝在一個對象中。在對象中聲明包是不允許的。[P55]
導入類型及其成員
在 Scala 中,使用
_
作為通配符。[P56]Scala 的
import
語句幾乎可以放在任何位置上,所以你可以將其可見性限制在需要的作用域中。[P56]-
可以在導入時對類型做重命名以解決沖突問題:[P56]
import java.math.BigInteger.{ ONE => _, // 重命名為下劃線,使該常量不可見。 TEN, ZERO => JAVAZERO }
導入是相對的
import collection.immutable._ // 由于 scala 已經默認導入,所以不需要給出全路徑
import _root_.scala.collection.parallel._ // 從 ”根“ 開始的全路徑
使用第二種導入方法時,要保證庫的所在路徑被包含在了 CLASSPATH 中。[P57]
包對象
-
Scala 支持包對象一種特殊類型的、作用域為包層次的對象。它像普通的對象一樣聲明,但與普通對象有著一些不同點:[P57]
- 文件名必須是 package.scala 。 - 1
- 標記上層包的作用域。 - 2
- 使用
package
關鍵字給包名之后的對象命名。 - 3 - 適合暴露給客戶端的成員。 - 4
// src/com/example/json/package.scala // 1 package com.example // 2 package object json { // 3 class JSONObject { ... } // 4 def fromString(string: String): JSONObject = { ... } } // 客戶端可以使用 import com.example.json._ 導入所有定義,或用通常方法單獨導入元素。
抽象類型與參數化類型
-
Scala 的參數化類型與 Java 的泛型很像。舉一個參數化類型的例子:[P58]
- 在 Scaladoc 中,
List
的聲明為sealed abstract class List[+A]
。 -
A
之前的+
表示:如果B
是A
的子類型,則List[B]
也是List[A]
的子類型,這被稱為協類型。 - 如果類型參數前有
-
,則表示另一種關系:如果B
是A
的子類型,且Foo[A]
被聲明為Foo[-A]
,則Foo[B]
是Foo[A]
的父類型(稱為逆類型)。
- 在 Scaladoc 中,
Scala 還支持一種被稱為”抽象類型“ 的抽象機制,可以用在許多參數化類型中。[P58]
-
參數化類型和抽象類型都被聲明為其他類型的成員,就像是該類型的方法與屬性一樣。[P58 - 59]
// 類型成員( type ) abstract class BulkReader { type In val source: In def read: String // 不同的 In ,read 的方式不同。 } class StringBulkReader extends BulkReader { type In = String }
// 參數化類型 abstract class BulkReader[In] { val source: In ... } class StringBulkReader extends BulkReader[String] = { ... }
-
類型成員比參數化類型的優勢:[P59]
- 當類型參數與參數化類型無關時,參數化類型更適用。
- 舉個例子:
List[A]
,A
可能是Int
、String
等。
- 舉個例子:
- 當類型成員與所封裝的類型同步變化時,類型成員更適用。有時這種特點被稱為家族多態,或者協特化。
- 當類型參數與參數化類型無關時,參數化類型更適用。