添加成员变量
本篇继续上一篇,前面我们定义了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之前。