dd

Scala开发教程(9): 类和对象 (四)

jerry Scala 2015年11月25日 收藏

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

dd