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

scala函数式对象

山东茌平一中迎来建校六十周年

相关推荐