添加成员变量
本篇继续上一篇,前面我们定义了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)
- }
实际上编译器会给出如下编译错误:
- <console>:11: error: value d is not a member of Rational
- new Rational(n*that.d + that.n*d,d*that.d)
- ^
- <console>:11: error: value d is not a member of 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 = 1
可以看到,这是就可以使用.number 等来访问类的成员变量。
自身引用
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就无法省略。
辅助构造函数
在定义类时,很多时候需要定义多个构造函数,在Scala中,除主构造函数之外的构造函数都称为辅助构造函数(或是从构造函数),比如对于Rational类来说,如果定义一个整数,就没有必要指明分母,此时只要整数本身就可以定义这个有理数。我们可以为Rational定义一个辅助构造函数,Scala定义辅助构造函数使用 this(?)的语法,所有辅助构造函数名称为this.
- def this(n:Int) = this(n,1)
所有Scala的辅助构造函数的第一个语句都为调用其它构造函数,也就是this(?),被调用的构造函数可以是主构造函数或是其它构造函数(最终会调用主构造函数),这样使得每个构造函数最终都会调用主构造函数,从而使得主构造函数称为创建类单一入口点。在Scala中也只有主构造函数才能调用基类的构造函数,这种限制有它的优点,使得Scala构造函数更加简洁和提高一致性。
私有成员变量和方法
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之前。