Scala函数式对象-有理数
有理數類的表示
實現規范:支持有理數的加減乘除,并支持有理數的規范表示
1.定義Rational
首先,考慮用戶如何使用這個類,我們已經決定使用“Immutable”方式來使用Rational對象,我們需要用戶在定義Rational對象時提供分子和分母。
class Rational(n:Int, d:Int)可以看到,和Java不同的是,Scala的類定義可以有參數,稱為類參數,如上面的n、d。Scala使用類參數,并把類定義和主構造函數合并在一起,在定義類的同時也定義了類的主構造函數。因此Scala的類定義相對要簡潔些。
Scala編譯器會編譯Scala類定義包含的任何不屬于類成員和類方法的其它代碼,這些代碼將作為類的主構造函數。比如,我們定義一條打印消息作為類定義的代碼:
scala> class Rational (n:Int, d:Int) { | println("Created " + n + "/" +d) | } defined class Rationalscala> new Rational(1,2) Created 1/2 res0: Rational = Rational@22f34036可以看到創建Ratiaonal對象時,自動執行類定義的代碼(主構造函數)。
2.重新定義類的toString方法
上面的代碼創建Rational(1,2),Scala 編譯器打印出Rational@22f34036,這是因為使用了缺省的類的toString()定義(Object對象的),缺省實現是打印出對象的類名稱+“@”+16進制數(對象的地址),顯示結果不是很直觀,因此我們可以重新定義類的toString()方法以顯示更有意義的字符。
在Scala中,你也可以使用override來重載基類定義的方法,而且必須使用override關鍵字表示重新定義基類中的成員。比如:
scala> class Rational (n:Int, d:Int) { | override def toString = n + "/" +d | } defined class Rationalscala> val x= new Rational(1,3) x: Rational = 1/3scala> val y=new Rational(5,7) y: Rational = 5/7?
3.前提條件檢查
前面說過有理數可以表示為 n/d(其中d、n為正數,而d不能為0)。對于前面的Rational定義,我們如果使用0,也是可以的。
怎么解決分母不能為0的問題呢?面向對象編程的一個優點是實現了數據的封裝,你可以確保在其生命周期過程中是有效的。對于有理數的一個前提條件是分母不可以為0,Scala中定義為傳入構造函數和方法的參數的限制范圍,也就是調用這些函數或方法的調用者需要滿足的條件。Scala中解決這個問題的一個方法是使用require方法(require方法為Predef對象的定義的一個方法,Scala環境自動載入這個類的定義,因此無需使用import引入這個對象),因此修改Rational定義如下:
scala> class Rational (n:Int, d:Int) { | require(d!=0) | override def toString = n + "/" +d | }defined class Rational scala> new Rational(5,0) java.lang.IllegalArgumentException: requirement failed at scala.Predef$.require(Predef.scala:211) ... 33 elided可以看到,如果再使用0作為分母,系統將拋IllegalArgumentException異常。
4.添加成員變量
前面我們定義了Rational的主構造函數,并檢查了輸入不允許分母為0。下面我們就可以開始實行兩個Rational對象相加的操作。我們需要實現的函數化對象,因此Rational的加法操作應該是返回一個新的Rational對象,而不是返回被相加的對象本身。我們很可能寫出如下的實現:
class Rational (n:Int, d:Int) { require(d!=0) override def toString = n + "/" +d def add(that:Rational) : Rational = new Rational(n*that.d + that.n*d,d*that.d)}實際上編譯器會給出編譯錯誤。
這是為什么呢?盡管類參數在新定義的函數的訪問范圍之內,但僅限于定義類的方法本身(比如之前定義的toString方法,可以直接訪問類參數),但對于that來說,無法使用that.d來訪問d。因為that不在定義的類可以訪問的范圍之內。此時需要定類的成員變量。
注:后面定義的case class類型編譯器自動把類參數定義為類的屬性,這是可以使用that.d等來訪問類參數)。
修改Rational定義,使用成員變量定義如下:
class Rational (n:Int, d:Int) { require(d!=0) val number =n val denom =d override def toString = number + "/" +denom def add(that:Rational) = new Rational( number * that.denom + that.number* denom, denom * that.denom )}?
要注意的我們這里定義成員變量都使用了val,因為我們實現的是“immutable”類型的類定義。number和denom以及add都可以不定義類型,Scala編譯能夠根據上下文推算出它們的類型。
scala> val oneHalf=new Rational(1,2) oneHalf: Rational = 1/2 scala> val twoThirds=new Rational(2,3) twoThirds: Rational = 2/3 scala> oneHalf add twoThirds res0: Rational = 7/6 scala> oneHalf.number res1: Int = 15.自身引用
Scala 也使用this來引用當前對象本身,一般來說訪問類成員時無需使用this,比如實現一個lessThan方法,下面兩個實現是等效的。
第一種:
def lessThan(that:Rational) = this.number * that.denom < that.number * this.denom第二種:
def lessThan(that:Rational) = number * that.denom < that.number * denom但如果需要引用對象自身,this就無法省略,比如下面實現一個返回兩個Rational中比較大的一個值的一個實現:
def max(that:Rational) = if(lessThan(that)) that else this其中的this就無法省略。
6.輔助構造函數
在定義類時,很多時候需要定義多個構造函數,在Scala中,除主構造函數之外的構造函數都稱為輔助構造函數(或是從構造函數),比如對于Rational類來說,如果定義一個整數,就沒有必要指明分母,此時只要整數本身就可以定義這個有理數。我們可以為Rational定義一個輔助構造函數,Scala定義輔助構造函數使用 this(…)的語法,所有輔助構造函數名稱為this。
def this(n:Int) = this(n,1)所有Scala的輔助構造函數的第一個語句都為調用其它構造函數,也就是this(…)。被調用的構造函數可以是主構造函數或是其它構造函數(最終會調用主構造函數)。這樣使得每個構造函數最終都會調用主構造函數,從而使得主構造函數稱為創建類單一入口點。在Scala中也只有主構造函數才能調用基類的構造函數,這種限制有它的優點,使得Scala構造函數更加簡潔和提高一致性。
7.私有成員變量和方法
Scala 類定義私有成員的方法也是使用private修飾符,為了實現Rational的規范化顯示,我們需要使用一個求分子和分母的最大公倍數的私有方法gcd。同時我們使用一個私有變量g來保存最大公倍數,修改Rational的定義:
scala> class Rational (n:Int, d:Int) { | require(d!=0) | private val g =gcd (n.abs,d.abs) | val number =n/g | val denom =d/g | override def toString = number + "/" +denom | def add(that:Rational) = | new Rational( | number * that.denom + that.number* denom, | denom * that.denom | ) | def this(n:Int) = this(n,1) | private def gcd(a:Int,b:Int):Int = | if(b==0) a else gcd(b, a % b) | } defined class Rational scala> new Rational ( 66,42) res0: Rational = 11/7注意gcd的定義,因為它是個回溯函數,必須定義返回值類型。Scala 會根據成員變量出現的順序依次初始化它們,因此g必須出現在number和denom之前。
8.定義運算符
本篇還將接著上篇Rational類,我們使用add定義兩個Rational對象的加法。兩個Rational加法可以寫成x.add(y)或者x add y。
即使使用 x add y還是沒有 x + y來得簡潔。
我們前面說過,在Scala中,運算符(操作符)和普通的方法沒有什么區別,任何方法都可以寫成操作符的語法。比如上面的 x add y。
而在Scala中對方法的名稱也沒有什么特別的限制,你可以使用符號作為類方法的名稱,比如使用+、-和*等符號。因此我們可以重新定義Rational如下:
這樣就可以使用 +、*號來實現Rational的加法和乘法。+、*的優先級是Scala預設的,和整數的+、-、*和/的優先級一樣。下面為使用Rational的例子:
scala> val x= new Rational(1,2) x: Rational = 1/2 scala> val y=new Rational(2,3) y: Rational = 2/3 scala> x+y res0: Rational = 7/6 scala> x+ x*y res1: Rational = 5/6從這個例子也可以看出Scala語言的擴展性,你使用Rational對象就像Scala內置的數據類型一樣。
9.Scala中的標識符
從前面的例子我們可以看到Scala可以使用兩種形式的標志符,字符數字和符號。字符數字使用字母或是下劃線開頭,后面可以接字母或是數字,符號“$”在Scala中也看作為字母。然而以“$”開頭的標識符為保留的Scala編譯器產生的標志符使用,應用程序應該避免使用“$”開始的標識符,以免造成沖突。
Scala的命名規則采用和Java類似的camel命名規則(駝峰命名法),首字符小寫,比如toString。類名的首字符還是使用大寫。此外也應該避免使用以下劃線結尾的標志符以避免沖突。
符號標志符包含一個或多個符號,如+、:和?。對于+、++、:::、<、 ?>、 :->之類的符號,Scala內部實現時會使用轉義的標志符。例如對:->使用$colon$minus$greater來表示這個符號。因此,如果你需要在Java代碼中訪問:->方法,你需要使用Scala的內部名稱$colon$minus$greater。
混合標志符由字符數字標志符后面跟著一個或多個符號組成,如 unary_+為Scala對+方法的內部實現時的名稱。
字面量標志符為使用‘’定義的字符串,比如 ‘x’ 、‘yield’ 。 你可以在‘’之間使用任何有效的Scala標志符,Scala將它們解釋為一個Scala標志符,一個典型的使用是 Thread的yield方法, 在Scala中你不能使用Thread.yield()是因為yield為Scala中的關鍵字, 你必須使用 Thread.‘yield’()來使用這個方法。
10.方法重載
和Java一樣,Scala也支持方法重載,重載的方法參數類型不同而使用同樣的方法名稱,比如對于Rational對象,+的對象可以為另外一個Rational對象,也可以為一個Int對象,此時你可以重載+方法以支持和Int相加。
def + (i:Int) = new Rational (numer + i * denom, denom)11.隱式類型轉換
上面我們定義Rational的加法,并重載+以支持整數,r + 2,當如果我們需要 2 + r如何呢?
可以看到 x + 3沒有問題,3 + x就報錯了,這是因為整數類型不支持和Rational相加。我們不可能去修改Int的定義(除非你重寫Scala的Int定義)以支持Int和Rational相加。如果你寫過.Net代碼,這可以通過靜態擴展方法來實現,Scala提供了類似的機制來解決這種問題。
如果Int類型能夠根據需要自動轉換為Rational類型,那么 3 + x就可以相加。Scala通過implicit def定義一個隱含類型轉換,比如定義由整數到Rational類型的轉換如下:
implicit def intToRational(x:Int) = new Rational(x)其實此時Rational的一個+重載方法是多余的, 當Scala計算 2 + r,發現 2(Int)類型沒有可以和Rational對象相加的方法,Scala環境就檢查Int的隱含類型轉換方法是否有合適的類型轉換方法,類型轉換后的類型支持+ r,一檢查發現定義了由Int到Rational的隱含轉換方法,就自動調用該方法,把整數轉換為Rational數據類型,然后調用Rational對象的 +方法。從而實現了Rational類或是Int類的擴展。關于implicit def的詳細介紹將由后面的文章來說明,隱含類型轉換在設計Scala庫時非常有用。
轉自:https://www.jianshu.com/p/61268f438485
轉載于:https://www.cnblogs.com/duanxz/p/9509748.html
總結
以上是生活随笔為你收集整理的Scala函数式对象-有理数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做梦梦到抽水抓鱼是什么意思
- 下一篇: mongoDB操作详细