scala函数式对象
1、构建Rational类
Rational是有理数,本章将通过有理数的构建进行深入的讲解
学习完本章可以掌握编写类库的一些方法,感觉像原始语言支持的那样,通过本章的学习你将可以使用如下例子
val oneHalf=new Rational(1,2)
val twoThirds=new Rational(3,4)
(oneHalf/7)+(1-twoThirds)
构建Rational类要求你把实例所需要的数据(参数分子和分母)提供好,如下开始:
class Rational(n:Int,d:Int)
这段代码没有类的定义体,所以不需要写花括号,圆括号内的n和d作为参数,scala类会把参数放在主构造方法内。
我们要想构造Rational类需要对程序进行改进。
scala> class Rational(n:Int,d:Int){
| println("Created:"+n+"/"+d)
| }
1
2
3
defined class Rational
在shell端可以直接使用:new Rational(2,3)
scala> new Rational(2,3)
Created:2/3
res0: Rational = Rational@5117dd67
可以看到有了分数的雏形
2、重新实现toString
以上的输出Rational@5117dd67。解释器通过Rational对象调用toString来获取以上看上去非常奇怪的字符串。Rational默认继承了java.lang.Object的toString,这里只是简单的打印了类名@符号和一个十六进制的数字。toString主要意图是帮助程序员调试输出的语句、日志信息、测试失败报告、解释器和调试器输出中给出的相应信息。但本例的toString没有意义,因为没有提供任何和Rational(有理数)相关的任何线索。我们可以重写toString
class Rational(n:Int,d:Int){
override def toString=n+"/"+d
1
}
override是重写的意思和java的概念相同。以上程序在shell进行直接调用
scala> new Rational(2,3)
res1: Rational = 2/3
3、前提条件检查
我们知道有理数或者说分数的分母不能为零,
scala> new Rational(2,0)
res2: Rational = 2/0
这种在现实是不允许的,所以在程序里我们也要处理。要将Rational的分母为0的情况设置为非法状态,当分母0作为参数传递进来时,不允许Rational被创建出来。
我们只需要对主构造函数进行值的前置约束即可。实现这个的方式是使用require。
class Rational(n:Int,d:Int){
require(d!=0)
override def toString=n+"/"+d
1
2
3
}
require会接受boolean的参数,传入为true返回正常结果,否则抛出java.lang.IllegalArgumentException异常
scala> new Rational(2,0)
java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:207)
… 34 elided
4、添加字段
主构造函数有了前置条件,接下来我们的注意力转向如何支持加法,我们可以在Rational类加一个add方法,方法内要接收另一个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)
}
1
2
3
4
5
6
7
8
9
10
11
}
这段代码是有问题的,会出以下错误:
value d is not a member of Rational
value n is not a member of Rational
虽然n和d在add方法的作用域内,但只能访问执行调用add的那个对象上的n和d的值。当在add中用到n和d时,编译器提供这些参数对应的值,但是不允许使用that.n或that.d,因为that并非执行你执行的add调用的那个对象。要解决这个问题,需要把that的分子和分母做成字段。
我们添加两个字段,numer和demon,接受两个参数n和d。内部的每个用到参数n和d的地方都要相应的改成numer和demon。如下:
class Rational(n:Int,d:Int){
require(d!=0)
val numer:Int=n
val demon:Int=d
override def toString=numer+"/"+demon
def add(that:Rational):Rational={
new Rational(
numer*that.demon+that.numer*demon,
demon*that.demon
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
}可以直接进行实例化。
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
res6: Rational = 7/6
再定义一个比较大小的方法:
def lessThan(that:Rational)={
this.numer*that.demon<that.numer*this.numer)
}
1
2
3
4
5
这里的this是可以省略的效果一样。
但下面不能省略:
def max(that:Rational)={
if(this.lessThan(that)) that else this
1
这里如果不写this就没有返回结果了。
5、添加第二个构造方法
当我们尝试给出一个参数的时候,我们的程序会怎么样呢?
scala> new Rational(3)
:12: error: not enough arguments for constructor Rational: (n: Int, d:
Int)Rational.
Unspecified value parameter d.
new Rational(3)
1
结果告诉我们给的参数不够,针对这一情况我们要进行处理,我们处理的手段是添加一个辅助的构造方法。
class Rational(n:Int,d:Int){
require(d!=0)
val numer:Int=n
val demon:Int=d
def this(n:Int)=this(n,1)
override def toString=numer+"/"+demon
def add(that:Rational):Rational={
new Rational(
numer*that.demon+that.numer*demon,
demon*that.demon
)
}
def lessThan(that:Rational)=
this.numer*that.demon<that.numer*this.numer
def max(that:Rational)={
if(this.lessThan(that)) that else this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
}
}
我们在测试程序中进行测试,可以看到能够接受一个参数了。
测试结果
3/1
false
6、私有字段和方法
以上程序我们只是简单的使用了n和d分别初始化了numer和demon。实际上我们的分子和分母的概念可能更加复杂,比如44/66简化后就是2/3,但是我们的构造函数没有进行处理。所以要两个数除以最大公约数。
class Rational(n:Int,d:Int){
require(d!=0)
private val g=gcd(n.abs,d.abs)
val numer:Int=n/g
val demon:Int=d/g
def this(n:Int)=this(n,1)
override def toString=numer+"/"+demon
def add(that:Rational):Rational={
new Rational(
numer*that.demon+that.numer*demon,
demon*that.demon
)
}
def lessThan(that:Rational)=
this.numer*that.demon<that.numer*this.numer //比较大小的函数
def max(that:Rational)={
if(this.lessThan(that)) that else this //取最大值的函数
}
private def gcd(a:Int,b:Int):Int={ //求最大公约数的函数
if(b==0)a else gcd(b,a % b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
}
为了保证g永远是正数,所以使用了abs求绝对值,把一个字段或者函数变成私有的只需要加上关键字private即可。最大公约数的方法对于一般程序员来说不是难题,不过多介绍。
object Test{
def main(args: Array[String]): Unit = {
val t1=new Rational(2,32)
val t2=new Rational(3,4)
println(new Rational(100,50))
println(t1 lessThan(t2))
1
2
3
4
5
6
7
}
}
使出结果:
2/1
false
7、操作符的定义
对于程序的使用者来说,希望更加简单的方式来使用,只是使用±*/即可。我们需要把我们的程序进行进一步的调整,把我们定义的所有的函数都使用操作符来代替,在scala里允许把函数(java的方法)名定义成+ - * /这样的符号。
class Rational(n:Int,d:Int){
require(d!=0)
private val g=gcd(n.abs,d.abs)
val numer:Int=n/g
val demon:Int=d/g
def this(n:Int)=this(n,1)
override def toString=numer+"/"+demon
def +(that:Rational):Rational={
new Rational(
numer*that.demon+that.numer*demon,
demon*that.demon
)
}
def *(that:Rational):Rational=
new Rational(numer*that.numer,demon*that.demon)
def <(that:Rational)=
this.numer*that.demon<that.numer*this.numer //比较大小的函数
def max(that:Rational)={
if(this.<(that)) that else this //取最大值的函数
}
private def gcd(a:Int,b:Int):Int={ //求最大公约数的函数
if(b==0)a else gcd(b,a % b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
}
针对已经定义好的 我们可以直接在测试程序中使用:
object Test{
def main(args: Array[String]): Unit = {
val t1=new Rational(2,32)
val t2=new Rational(3,4)
println(new Rational(100,50))
println(t1 <(t2))
println(t1 +t2)
println(t1 +t2*t1)
1
2
3
4
5
6
7
8
9
10
11
}
}
注意t1 +t2t1这个操作会被当成t1 +(t2t1)这样的方式来调用,因为*的优先级比+的优先级要高。在scala理还可以定义诸如++ – ::这样的操作符作为函数名。
8、方法重载
在我们的程序中,一个有理数乘一个正数效果不太理想,因为操作必须都是Rational,因此必须写成r new Rational(2),而不能写成b*2.通过添加两个新的方法来对有理数和正数做加法和乘法。
每个算数方法都提供两个版本:一个接收有理数作为参数,一个接收正数作为参数。也就是说每个算法都被重载了。
class Rational(n:Int,d:Int){
require(d!=0)
private val g=gcd(n.abs,d.abs)
val numer:Int=n/g
val demon:Int=d/g
def this(n:Int)=this(n,1)
override def toString=numer+"/"+demon
def +(that:Rational):Rational={
new Rational(
numer*that.demon+that.numer*demon,
demon*that.demon
)
}
def +(i:Int):Rational={
new Rational(numer+i*demon,demon)
}
def -(that:Rational):Rational={
new Rational(
numer*that.demon-that.numer*demon,
demon*that.demon
)
}
def -(i:Int):Rational={
new Rational(numer-i*demon,demon)
}
def *(that:Rational):Rational=
new Rational(numer*that.numer,demon*that.demon)
def *(i:Int):Rational=
new Rational(numer*i,demon)
def /(that:Rational):Rational=
new Rational(numer*that.demon,demon*that.numer)
def /(i:Int):Rational=
new Rational(numer,demon*i)
def <(that:Rational)=
this.numer*that.demon<that.numer*this.numer //比较大小的函数
def max(that:Rational)={
if(this.<(that)) that else this //取最大值的函数
}
private def gcd(a:Int,b:Int):Int={ //求最大公约数的函数
if(b==0)a else gcd(b,a % b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
}
程序编写好后可以在Test进行测试:
object Test{
def main(args: Array[String]): Unit = {
val t1=new Rational(2,32)
val t2=new Rational(3,4)
println(new Rational(100,50))
println(t1 <(t2))
println(t1 +t2)
println(t1*2)
println(t1/2)
println(t1 +t2*t1)
println(t1 *t2+t1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
}
}
运行结果如下:
2/1
false
13/16
1/8
1/32
7/64
7/64
山东茌平一中迎来建校六十周年