Scala编程(第20章:抽象成员)

1.抽象成员概述:如果类或特质的某个成员在当前类中没有完整的定义,那么它就是抽象的。下面这个特质声明了四种抽象成员:一个抽象类型(T)、一个抽象方法(transform)、一个val(initial)和一个var(current):

trait Abstract{
  type T
  def transform(x:T):T
  val initial:T
  var current:T
}

Abstract特质的具体实现需要填充每个抽象成员的定义。如:

class Concrete extends Abstract{
   type T = String
   def transform(x: T): T = x+x
   val initial: T = "hi"
   var current: T = initial
}

这个例子应该能给你一个粗略的关于Scala中都有什么样的抽象成员的概念。

 

2.类型成员:从前一节的例子当中,我们不难看出,Scala的抽象类型指的是用type关键字声明为某个类或特质的成员(但并不给出定义)的类型。类本身可以是抽象的,而特质本来从定义上讲就是抽象的,不过类和特质在Scala中都不叫抽象类型。

使用类型成员的原因之一是给真名冗长或含义不明显的类型定义一个短小且描述性强的别名。

 

3.抽象的val:抽象val的声明看上去很像是一个抽象的无参方法声明:

val initial:String

使用方代码可以用完全相同的方式(也就是obj.initial)来引用val和方法。不过如果initial是个抽象的val,使用方可以得到如下保证:每次对obj.initial的引用都会交出相同的值。如果initial是抽象方法,那么这个保证无法成立,因为这样一来initial可以被某个具体的每次都返回不同值的方法实现(如闭包)

换句话说,抽象val限制了它的合法实现:任何实现都必须是一个val定义;而不是def或var。从另一方面讲,抽象方法声明可以用具体的方法定义或具体的val定义实现(变得更具体):

abstract class Fruit{
  val v:String  //v表示值
  def m:String  //m表示方法
}

class Apple extends Fruit {
  val v:String ="Value"
  val m:String ="Method" //用val重写(覆盖)def是OK的
}

class BadApple extends Fruit {
  def v:String ="Value"  //错误:方法v需要是一个稳定的,不可变的值
  def m:String ="Method"
}

 

4.抽象的var:跟抽象val类似,抽象var也只声明了名称和类型,但并不给出初始值:

trait AbstractTime{
  var hour:Int
  var minute:Int
}

声明为类成员的var默认都带上了getter和setter方法。跟下面定义是等效的:

trait AbstractTime{
  def hour:Int         //hour的getter方法
  def hour_=(x:Int)    //hour的setter方法
  def minute:Int       //minute的getter方法
  def minute_:(x:Int)  //minute的setter方法
}

 

5.初始化抽象的val:抽象val有时会承担超类参数的职能:它们允许我们在子类中提供那些在超类中缺失的细节。这对于特质而言尤其重要,因为特质并没有让我们传入参数的构造方法。因此通常来说对于特质的参数化是通过子类中实现抽象val来完成的。如简化的Rational特质:

trait RationalTrait{
  val n:Int
  val d:Int
  println("trait")
  println(n+" "+d)
}

要实例化一个该特质的具体实例,需要实现抽象的val定义:

new  RationalTrait{
      override val n: Int = 1
      override val d: Int = 2
      val anonymousClass=println("Anonymous class")
    }

//trait
//0 0
//Anonymous class

这里的new关键字出现在特质名称RationalTrait之前,然后是用花括号括起来的类定义体。这个表达式交出的是一个混入了特质并由定义体定义的匿名类的实例,通过println标记。

这个特定的匿名类的实例化的作用跟new Rational(1,2)创建实例的作用相似。但表达式初始化的顺序有一些细微的差异:

new Rational(expr1,expr2)

expr1和expr2这两个表达式会在Rational类初始化之前被求值,这样expr1和expr2的值对于Rational类的初始化过程可见。对于特质而言,情况正好相反:

new  RationalTrait{
      override val n: Int = expr1
      override val d: Int = expr2
      val anonymousClass=println("Anonymous class")
    }

expr1和expr2这两个表达式是作为匿名类初始化过程的一部分被求值的,但是匿名类是在RationalTrait特质之后被初始化的。因此,在RationalTrait的初始化过程中,n和d的值并不可用(更确切地说,对两个值当中任何一个的选用都会交出类型Int的默认值,0)。当像第6章那样用到前置条件require语句:

require(d != 0)

这样时会报错,因为n和d初始化的时候还是默认值0,这让require的调用失败了。Scala提供了两种可选方案来应对这个问题: 预初始化字段惰性的val

预初始化字段:第一种方案,预初始化字段,让我们在超类被调用之前初始化子类的字段。只需要在被字段定义放在超类的构造方法之前的花括号之前即可:

new {
      override val n: Int = 1
      override val d: Int = n
      val anonymousClass=println("Anonymous class")
    } with RationalTrait

//Anonymous class
//trait
//1 1

预初始化字段并不仅仅局限于匿名类,它们也可以被用在对象或具名子类中:

object twoThirds extends {
      override val n: Int = 2
      override val d: Int = 3
      val anonymousClass=println("Anonymous class")
    } with RationalTrait
//无打印

类定义中的预初始化字段: 

    class RationalClass(n:Int,d:Int) extends {
      override val numerArg: Int = n
      override val denomArg: Int = d
      val anonymousClass=println("Anonymous class")
    } with RationalTrait {
      println("Class body")
    }

惰性的val:如果我们在val定义之前加上lazy修饰符,那么右侧的初始化表达式就只会在val第一次被使用时求值。定义一个Demo对象:

object Demo{
  val x={println("initializing");"done"}
}
Demo
//打印:initializing

将x定义为lazy:

object Demo{
  lazy val x={println("initializing");"done"}
}
Demo
//无打印
Demo.x
//打印:initializing

现在,Demo的初始化并不涉及对x的初始化。对x的初始化被延迟到第一次访问x的时候。这跟x用def定义成无参方法的情况类似。不过,不同于def,惰性的val永远不会被求值多次。事实上,在对惰性的val首次求值之后,其结果会被保存起来,在后续的使用当中,都会复用这个相同的val。重新编写RationalTrait:

trait RationalTrait{
  val numerArg:Int
  val denomArg:Int
  lazy val numer: Int = numerArg / g
  lazy val denom: Int = denomArg / g
  override def toString: String = numer + "/" + denom
  private lazy val g={
    require(denomArg!=0)
    gcd(numerArg,denomArg)
  }
  private def gcd(a: Int, b: Int): Int = {
    if (b == 0) a else gcd(b, a % b)
  }
}

在调用toString方法的时候调用numer和denom,从而调用g,完成初始化过程。

 

6.抽象类型:“type T”这个抽象类型的声明,T指的是一个声明时还未知的类型。不同的子类可以提供不同的T的实现。定义Animal吃Food的抽象类:

class Food
abstract class Animal {
  type SuitableFood <:Food
  def eat(food:SuitableFood)
}

有了Animal的定义,现在可以定义牛吃草:

class Grass extends Food
class Cow extends Animal {
  type SuitableFood = Grass
  def eat(food:SuitableFood)={}
}

还可以这样创建已经确定类型的抽象类型:

class Grass(val s:String) extends Food
val c=new Cow
val g=new c.SuitableFood("Grass")
println(g.s)

//打印:Grass

但不可以创建未确定类型的抽象类型:

abstract class Animal {
  type SuitableFood <:Food
  def eat(food:SuitableFood)
  def suitable=new SuitableFood("Grass")   //报错
}

 

7.枚举:别的一些语言,包括Java和C#,都有内建的语法结构来定义枚举类型。Scala并不需要特殊的语法来表示枚举,而是在标准类库中提供了一个类:scala.Enumeration。如:

object Direction extends Enumeration{
  val North= Value("North")
  val East= Value("East")
  val South= Value("South")
  val West= Value("West")
}
println(Direction.East.id)
println(Direction(1))

//1
//East

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值