有了 Scala 基礎(4)—— 類和對象 的前提,如今就能夠來構建一個基於 Scala 的函數式對象。html
下面開始構造一個有理數對象 Rational。java
1. 主構造方法和輔助構造方法ide
對於每個類的定義,Scala 只容許一個主構造方法,主構造方法的入參緊跟在類定義的後面:函數
class Rational(n: Int, d: Int) { } val r = new Rational(1,2) // 有理數 1/2
你可能須要一個分母 d 被預約義爲 1 的一個構造方法,這事就須要用到輔助構造方法(auxiliary constructor)。post
輔助構造方法以 def this(...) 開始:ui
class Rational(n: Int, d: Int) { def this(n: Int) = this(n, 1) } val r = new Rational(5) // 有理數5
2. 前置條件this
你可能須要對初始化一個對象進行一些參數檢查,例若有理數的分母 d 不能爲 0。url
在每個 Scala 文件中都會自動引用 Predef 這個獨立對象,裏面有 require() 方法可以知足這個需求。spa
class Rational(n: Int, d: Int) { require(d != 0)
def this(n: Int) = this(n, 1) } val r = new Rational(5, 0) // 會拋出異常
3. toString() 方法htm
對於一個類來講,重寫 toString() 方法是一個很常見的需求。
須要注意的是 Scala 對於重寫父類方法,會強制加上 override,而在 Java 中這一行爲是 optional 的。
class Rational(n: Int, d: Int) { require(d != 0)
def this(n: Int) = this(n, 1)
override def toString: String = n + "/" + d // 去掉 override 會編譯報錯 }
4. 添加字段和方法
在這裏爲 Rational 類添加一個 add() 方法,這裏會出現一個初學者常見的錯誤:
class Rational(n: Int, d: Int) { require(d != 0) def this(n: Int) = this(n, 1) override def toString: String = n + "/" + d def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d) }
這段邏輯看似沒有問題,並且編譯期間也不會報錯,可是運行時卻會報錯。
緣由在於,雖然當前對象 this 能夠很天然地使用構造方法傳入的 n 和 d,可是 that 對象卻不能這麼使用。
這時就要增長字段:
class Rational(n: Int, d: Int) { require(d != 0) val numer: Int = n val denom: Int = d def this(n: Int) = this(n, 1) override def toString: String = n + "/" + d def add(that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) }
對於有理數來講,咱們不但願出現 2/10 這樣的數字,而是 1/5,這時就要增長私有的方法 gcd 來求最大公約數,以及私有字段 g 存儲最大公約數:
class Rational(n: Int, d: Int) { require(d != 0) private val g = gcd(n.abs, d.abs) val numer: Int = n / g val denom: Int = d / g def this(n: Int) = this(n, 1) override def toString: String = numer + "/" + denom def add(that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) }
5. 定義操做符
Scala 能夠用定義方法一樣的方式定義操做符,達到相似「操做符重載」的效果。
例如,add 方法能夠用 + 操做符代替:
class Rational(n: Int, d: Int) { require(d != 0) private val g = gcd(n.abs, d.abs) val numer: Int = n / g val denom: Int = d / g def this(n: Int) = this(n, 1) override def toString: String = numer + "/" + denom def +(that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) } val r1 = new Rational(5, 10) val r2 = new Rational(3, 10) println(r1 + r2) // 能夠用+操做符,結果是 4/5
由此處可見,Scala 中的標識符分爲2種:
6. 方法重載
與 Java 相同,Scala 支持相同方法名,不一樣的參數列表的方法重載:
class Rational(n: Int, d: Int) { require(d != 0) private val g = gcd(n.abs, d.abs) val numer: Int = n / g val denom: Int = d / g def this(n: Int) = this(n, 1) override def toString: String = numer + "/" + denom def +(that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) def +(that: Int): Rational = new Rational(numer + that * denom, denom) private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) }