《Scala By Examples》第6章 类和对象

先定义了一个“有理数”(rational)类:

package ch6_classes_and_objects

/**
 * 以Class的形式定义一个有理数的类型
 */
class Rational(n : Int , d : Int) {
  
  /**
   * 求公约数
   */
  private def gcd( x : Int , y : Int) : Int = {
    if ( x == 0 ) y 
    else if ( x < 0 ) gcd ( -x , y )
    else if ( y < 0 ) -gcd ( x , -y)
    else gcd ( y % x , x)    
  }
  
  private val g = gcd ( n , d) // 分子,分母的最大公约数
  
  val number : Int = n / g 
  val denom : Int = d / g
  
  def +(that : Rational) = 
    new Rational(number * that.denom + that.number * denom , denom * that.denom)
  
  def -(that : Rational) = 
    new Rational(number * that.denom - that.number * denom , denom * that.denom)

}

私有成员:这个类里定义一个私有方法:gcd,用来计算最大公约数。还有一个私有成员g ,用来持有构造参数(n , d)的最大公约数。这些变量在Rational类外部是不能访问到的。这些是用来保证分子(numerator)和分母(denominator)在一个正常的格式里。

创建和访问对象:以下的例子用来展示如何使用这个类的:

var i = 1 
var x = new Rational(0 , 1)
while(i < = 10){
  x += new Rational(1 , i )
  i += 1
}
println("" + x.number + "/" + x.denom)

继承(Inheritance)和重写(Overriding):Scala里每个类都有一个超类(superClass)。如果没有明写出来,默认就是继承自scala.AnyRef类。一个类会继承父类所有的成员,当然,它也可以再重写(override)它们。比如toString方法。不过要注意的是,跟Java不同的是,Scala里,重定义的要在变量要在前面加上一个override标识符。

  与Java里的继承一样,如果A类继承自B类,那么A可以用在所在需要B类型的地方。说类型A遵从(conform to)类型B。也即如下正确:

var x : AnyRef = new Rational(1 , 2)

省略参数的方法(Parameterless Methods):不像Java,Scala里的方法不一定要带参数列表。如下面的square方法:

def square = new Rational(number * number , denom * denom)

val r = new Rational(3 , 4)
println(r.square)

也就是说,无参的方法可以像值参数一样的访问。值和无参方法间的不同就是在于它们的定义。val的右侧值在对象被创建时就被计算出来,并且不再变换。而无参方法右侧的值只是在每次被调用的时候再进行计算。这种对属性和无参方法进行统一访问的方式使类实现变得可扩展。通常,一个版本中的一个属性会在下个版本中变成计算得到的值。统一访问保证了客户端不会因这种变化而重写。

抽象类

abstract class IntSet{
  def incl(x : Int) : IntSet
  def contains(x : Int) : Boolean
}

IntSet这个类被标记为abstract class . 它有两个结果。首先,抽象类可能有声明的(declared)但没有实现的委托(deferred)成员。在上面这个例子里,incl和contains就是这样的成员。其次,因为一个抽象类可能有未实现的成员,所以不能用new 来创建一个这个类的对象。相反地,抽象类可以用来作为其他类的基本类(base class ),这些子类要实现这个委托成员(deferred members)。

特质(trait):abstract class 类似的,在Scala里也可以用关键字trait 来表示。特质是用来被加到其他类里的抽象类。另一个应用场景,就像Java里的接口(interface)一样,用来收集一些不同类中的通用功能。也可以如下定义上面的IntSet类

trait IntSet{
  def incl(x : Int) : IntSet
  def contains(x : Int) : Boolean
}

实现抽象类:现在我们打算用二叉树来实现集合。有两种可能性,一种树是空集合(empty set),另一种是由一个整数和两个子树组成。以下是实现的代码:

EmptySet:

class EmptySet extends IntSet {

  def incl(x: Int): IntSet = { null }

  def contains(x: Int): Boolean = false 

}

NonEmptySet:

class NonEmptySet( elem : Int , left : IntSet , right : IntSet) extends IntSet {

  def incl(x: Int): IntSet = 
    if ( x < elem) new NonEmptySet(elem , left incl(x) , right)
    else if ( x > elem ) new NonEmptySet(elem , left , right incl(x))
    else this

  def contains(x: Int): Boolean = 
    if ( x < elem) left contains x 
    else if ( x > elem ) right contains x
    else true 

}

两个类都是继承自IntSet,所以都可以用在所在需要IntSet类型的地方。

动态绑定(dynamic binding):面向对象的语言都是用动态发送(dynamic dispatch)的方式给方法的调用。即调用一个方法依赖于包含这个方法的对象的运行进类型(run-time type)。(可以理解成类似于多态的意思?)Scala把所有的函数都当成对象来对待。

对象(objects):在之前的代码里,用表达式:new EmptySet 来创建了一个EmptySet的对象,所以,可以在任何需要空集合的时候通过这种方式来创建一个空集合。例如:

val EmptySetVal = new EmptySet

来创建一个新的空集合。但有一个问题:这么定义的值不是一个合法的Scala顶级(top-level)定义。因为它必须成为另一个类或对象的某个部分。一个更为直接点的方法,就是用object 来作定义。以下是一个合理化的可选的定义一个空集合对象:

object EmptySet extends IntSet{
  def contains(x : Int) : Boolean = false
  def incl(x : Int) : IntSet = new NonEmptySet(x , EmptySet , EmptySet)
}

因为它可以不用像类一样的使用new 来创建,而是可以直接使用(注:有点类似于Java中的静态类),那这个对象是何时创建的?回答是它其中的一个成员第一次被访问的时候,这个对象就创建出来了。这个策略叫作延迟赋值(lazy evaluation。是不是也可以叫“懒加载”)。

标准类:Scala是一个纯粹的面向对象的语言。意味着Scala里的所有值都可以当成对象。甚至连基本类型int , boolean都没有特殊对待。它们被当成Scala类的简写,被定义在Predef中:

type boolean = scala.Boolean
type int = scala.Int
type long = scala.Long

(之后给出了标准库中Boolean 对象的定义。略过)。还举例了一个“自然数”的定义。之后再看。

 

 

 

 

 

posted @ 2012-04-11 10:07  peterZ.D  阅读(747)  评论(0)    收藏  举报