说明
本节通过构建一个有理数(Rational)的类来说明函数式对象,有理数含以下说明:
- 所有的有理数都可以通过比率形式 n/d
来表示
- d != 0
- 有理数的加法首先通分再分子相加
- 比较大小首先通分后比较分子大小
- 有理数的约简是分子分母同时除于其最大公约数
- 有理数的乘法直接分子分母对应相乘
- 按照数学上有理数不可变的性质,经过计算的值应当为新值返回
创建 Rational 类
scala> class Rational(n: Int, d: Int)
defined class Rational
值得注意的点:
- 当构建的类没有实体时,可以省略大括号{}
- scala 的主构造器参数(类参数)直接在类名后面传入,scala解释器会自动将其构成主构造器(不熟悉的构造器的可以找 java 相关资料学习)
重载 toString 方法
接下来试着建立一个对象
scala> val x = new Rational(1,2)
x: Rational = Rational@87220f1
可以看到,Rational 的构造器返回了一串字符,这段字符是有默认方法 toString
实现,可以将其重载成适合我们的方法
scala> class Rational(n: Int, d: Int) {
| override def toString = n + "/" + d
| }
defined class Rational
再建立一个对象,可以看到重载后的效果
scala> val x = new Rational(1, 2)
x: Rational = 1/2
检查先决条件
这里的先决条件是指分母不能为 0,可以使用 require 方法,当传入的值为 false 时,将抛出 IllegalArgumentException
scala> class Rational(n: Int, d: Int) {
| require(d!=0)
| override def toString = n + "/" + d
| }
加法运算
定义 add 方法实现加法,参数为另一个 Rational 对象,并返回一个新的 Rational 对象以保持原对象的不变性
scala> class Rational(n: Int, d: Int) {
| require(d!=0)
| val number: Int = n
| val denom: Int = d
| override def toString = number + "/" + denom
|
| def add(that: Rational): Rational =
| new Rational(
| number*that.denom + that.number*denom,
| denom*that.denom
| )
| }
这里新增了两个字段,值分别为两个类参数,是有原因的,比如说 x.add(y)
运算,x 为调用方法者,使用 add 方法,其自身的类参数 n 和 d 自然可以提供到运算中,但 y 并不是调用者,所以其类参数是提供不了的,编译会出错,换成字段的话,就可以使用其字段属性了
另外,在类外部也是不能直接访问类参数的,换成段就可以
scala> x.n
<console>:13: error: value n is not a member of Rational
x.n
^
scala> x.number
res3: Int = 1
使用 add
方法
scala> x.add(y)
res4: Rational = 4/4
scala> x add y
res5: Rational = 4/4
其中第二种方法就像前面所记录的那样,x + y
的本质是 (x).+(y)
大小比较
这里引入自指向关键字 this
,其表示指向当前执行方法被调用的对象实例
def lessThan(that: Rational) =
this.number*that.denom < that.number*this.denom
从 add
方法中可以看到,这个 this 其实是可以省略的,不过下面的第二个 this 是不能省略的
def max(that: Rational) =
if(this.lessThan(that)) that else this
辅助构造器
对于有些特殊的有理数,比如分母为 1 的,是否可以直接输入分子就行了呢,辅助构造器可以完成这个
scala> class Rational(n: Int, d: Int) {
| require(d!=0)
| val number: Int = n
| val denom: Int = d
|
| def this(n: Int) = this(n, 1)
|
| override def toString = number + "/" + denom
| }
scala> val x = new Rational(4)
x: Rational = 4/1
scala 中的每一个辅助构造器的第一个动作都是调用同类的别的构造器开始,最后的调用终将结束于对构造器的调用,这里是直接调用了主构造器 this(n, 1)
约简
对于有些某些非最简分数(66/18)这类,我们可以对其进行约分,首先我们需要找出其最大公约数(考虑到有负数的情况,只传入绝对值计算),再将类参数约简,由于只在类内部使用,将其定义为私有变量或方法
scala> class Rational(n: Int, d: Int) {
| require(d!=0)
| private val g = gcd(n.abs, d.abs)
| val number: Int = n/g
| val denom: Int = d/g
|
| def this(n: Int) = this(n, 1)
|
| override def toString = number + "/" + denom
|
| private def gcd(a: Int, b: Int): Int =
| if(b==0) a else gcd(b, a%b)
| }
scala> val x = new Rational(66, 18)
x: Rational = 11/3
之后,每个 Rational 对象都以最简分数的形式构造,很牛逼!
定义操作符
在正常使用情况下,显然 x + y
会比 x add y
更容易接受使用,对比可以知道,加上 +
是 scala 合法的标识符,将其作为方法名就行了,这里顺便加上乘法运算
def +(that: Rational): Rational =
new Rational(
number*that.denom + that.number*denom,
denom*that.denom
)
def *(that: Rational): Rational =
new Rational(number*that.number,denom*that.denom)
测试如下:
scala> x * y
res6: Rational = 11/4
scala> x + y
res7: Rational = 53/12
另外,碰到同时含有 +、* 运算的,scala会自动进行优先级使用,这个有点难接受,包括括号的
scala> x + x * y
res8: Rational = 7/2
scala> x + (x * y)
res9: Rational = 7/2
scala> (x + x) * y
res10: Rational = 11/2
方法重载
目前,Rational 只能实现有理数和有理数的运算,不能直接实现有理数和整数的运算,比如计算 x * 2
需要 x * Rational(2)
才能间接实现,未免太过繁琐
可以使用方法重载进行实现,顺便加入减法和除法的功能
def +(that: Rational): Rational =
new Rational(
number*that.denom + that.number*denom,
denom*that.denom
)
def +(i: Int): Rational =
new Rational(number + i * denom, denom)
def -(that: Rational): Rational =
new Rational(
number*that.denom - that.number*denom,
denom*that.denom
)
def -(i: Int): Rational =
new Rational(number + i * denom, denom)
def *(that: Rational): Rational =
new Rational(number*that.number,denom*that.denom)
def *(i: Int): Rational =
new Rational(number * i, denom)
def /(that: Rational): Rational =
new Rational(number*that.denom,denom*that.number)
def /(i: Int): Rational =
new Rational(number, denom * i)
scala 中,方法的重载与 Java 类似,都是根据不同的参数查找需要的方法
结尾
隐式转换的内容接下来再补充