通常認為 functional object 的特性,就是使用了 immutable object 的特性。
immutable vs mutable object
immutable objects 比 mutables obejcts 多了幾個優點, 但有一個缺點。
優點是
immutable objects 比 mutable objects 容易理解,因為他們不會隨著時間改變。
可任意傳送 immutable objects,但如果是 mutable table,則可能需要複製一份,以防止被其他程式碼修改。
因為不能修改 immutable object 的內容,所以兩個 threads 不能同時存取一個 immutable object。這在 multi-thread 環境會很好用。
immutable objects 可安全地用在 hash table key 裡面,才不會發生這種情況:當 mutable object 用在 HashSet,下次使用 HashSet 時,物件可能就不見了。
缺點是
- immutable objects 需要較大的一塊記憶體空間,進行物件複製。
製作一個 Rational Number Immutable Object
// primary constructor,需要兩個 整數,分別是分子與分母
class Rational(n: Int, d: Int) {
// scala 會直接將 class body 裡面,不屬於任何 field/method 的程式碼
// 直接放到 primary constructor
// scala 限制更多, 只能讓 primary constructor,
// 在第一行呼叫 superclass constructor
// require 語法是 preconditions
// 因為有理數的分母不能為 0,必須在建立物件時,加上欄位檢查
require(d != 0)
// 66/42 可約分成 11/7
// 需要計算 gcd(66,42) greatest common divisor
private val g = gcd(n.abs, d.abs)
// 外部可直接使用 r.numer, r.denom 兩個欄位的數值
val numer = n / g
val denom = d / g
// auxiliary constructors
// 5/1 原本要寫 Rational(5,1) 可省略成 Rational(5)
// 任何 auxiliary constructor 都可以在第一行 呼叫其他 constructor
def this(n: Int) = this(n, 1)
// 有理數的加法,為了保持 immutable object 的特性
// 運算後,回傳新的 Rational object
// 定義 operator method
def +(that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom)
def +(i: Int): Rational =
new Rational(numer + i * denom, denom)
def -(that: Rational): Rational =
new Rational(
numer * that.denom - that.numer * denom,
denom * that.denom)
def -(i: Int): Rational =
new Rational(numer - i * denom, denom)
def *(that: Rational): Rational =
new Rational(numer * that.numer, denom * that.denom)
// Rational 要處理 r*2 的運算,必須要對 * 作 overloaded
def *(i: Int): Rational =
new Rational(numer * i, denom)
def /(that: Rational): Rational =
new Rational(numer * that.denom, denom * that.numer)
def /(i: Int): Rational =
new Rational(numer, denom * i)
// 列印物件時,會自動呼叫 toString,否則預設列印出物件的 reference 位址
// 這裡是覆寫 override 上層的 toString
override def toString = numer + "/" + denom
// gcd 最大公因數 greatest common divisor
private def gcd(a: Int, b: Int): Int =
if (b == 0) a else gcd(b, a % b)
}
identifier in scala
有四種
alphanumeric identifier
以 或 a letter 開始, 後面可以是 letters/digits/,$ 也算是一個 character,但這是保留給 scala compiler 使用,建議不要使用 $ , 以免跟 compiler 產生的 id 有衝突。operator identifier
包含 1~多 個 operator characters (ex: + : ? ~ # )
ex: + ++ ::: <?> :->mixed ifentifier
包含 a alphanumberic identifier, 後面是 , 然後是 operator identifier。
ex: unary+ myvar_=literal identifier
用 (...
) 包含在裡面的任意 string
ex:x
<clinit>
yield
yield 是 java Thread class 的 static method,但在 scala 不能寫 Thread.yeild(),因為 yield 是 scala 的保留字,所以要改寫成 Thread.yield
()。
implicit conversion
上面的 Rational 可支援 r2,但是無法支援 2r 的計算,為了解決這個問題,scala 讓我們建立一個 implicit conversion,自動轉換 integer 為 rational number。
implicit conversion 定義有 scope 的限制,如果要在 scala interpreter 裡面使用,則必須在 interpreter 裡面執行 implicit conversion 的定義。如果把 implicit method 定義放在 Rational class 裡面,則對 inpterpreter 來說,是沒有作用的。
implicit def intToRational(x: Int) = new Rational(x)
測試
scala> val r = new Rational(2,3)
r: Rational = 2/3
scala> 2 * r
res0: Rational = 4/3
雖然 implicit conversion 很好用,但可能會造成程式可讀性降低。
scala 的程式特性是簡潔,但在簡化程式碼的時候,同時要考慮到 readable 與 understandable 的要求,才不至於寫出難以維護的程式碼。