《Programming in Scala 3rd》阅读笔记

Chapter 20 《Abstract Members》

2018-07-06  本文已影响2人  liqing151

抽象成员概述

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

初始化抽象val

trait RationalTrait {
val numerArg: Int
val denomArg: Int
}
new RationalTrait {val numerArg = 1;val denomArg = 2}

new出现在特质名称RationalTrait之前,然后是用花括号括起来的定义体。这个表达式交出的是一个混入了特质并由定义体定义的匿名类的实例。表达式初始化的顺序有一些细微的差异。new Rational(expr1, expr2)expr1expr2会在类Rational初始化之前被求值,这样expr1expr2对于Rational类的初始化过程是可见的。对于特质而言,

new RationalTrait {val numerArg = 1;val denomArg = 2}

expr1expr2这两个表达式是作为匿名类初始化过程中的一部分被求值的,但是这个匿名类是在RationalTrait特质之后被初始化的。因此,在RationalTrait的初始化过程中,expr1expr2都为0,不可用。说明了类参数和抽象字段初始化顺序的差异。解决这个问题有两种方式:预初始化字段和懒加载val字段。

object twoThirds extends {
val numerArg = 2
val denomArg = 3
} with RationalTrait

还有一种更为通用的写法

class RationalClass(n: Int, d: Int) extends {
val numerArg = n
val denomArg = d
} with RationalTrait {
def + (that: RationalClass) = new RationalClass(
numer * that.denom + that.numer * denom,
denom * that.denom
)
}

由于初始化字段在超类的构造方法之被调用,因此使用this的时候,this指向的不是{}本身,而是new{}这个对象。初始化字段的行为类似于类参数,相当于class Test(a: Int)这样的形式,a是类参数,但不是类中的字段。

scala> new {
val numerArg = 1
val denomArg = this.numerArg * 2
} with RationalTrait
<console>:11: error: value numerArg is not a member of object $iw
val denomArg = this.numerArg * 2
^```
trait LazyRationalTrait {
val numerArg: Int
val denomArg: Int
lazy val numer = numerArg / g
lazy val denom = denomArg / g
override def toString = 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)
}
使用 
new LazyRationalTrait {
val numerArg = 1 
val denomArg = 2 
}
res7: LazyRationalTrait = 1/2

这样的代码完全没问题,因为在特质中涉及到需要子类覆盖的字段都是lazy的,只有在第一次被访问时才进行初始化。

初始化过程。1.LazyRationalTrait初始化;2.需要new对象的匿名类初始化;3.解释器调用对象的toString方法进行打印:触发numer初始化,numerArg已经被初始化为1,触发g初始化,denomArg已经被初始化为2g完成初始化,numer初始化完成,toString中继续触发denom


抽象类型

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

class Grass extends Food
class Cow extends Animal {
    override def eat(food: Grass) = {} // This won't compile
}
class Fish extends Food
val bessy: Animal = new Cow
bessy eat (new Fish)
 // ...you could feed fish to cows. // 错误

可以使用抽象类型来完成精确的建模:

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

至于具体的Animal该吃什么食物,在Animal这个层次并不能确定。定义抽象类型用于子类各自实现。

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

这时候使用bessy eat (new Fish)会编译出错,SuitableFood类型是不对的。


路径依赖类型


改良类型

Animal { type SuitableFood = Grass }
val animals = List[Animal { type SuitableFood = Grass }](new Cow)

枚举

object Color extends Enumeration {
val Red = Value
val Green = Value
val Blue = Value
} 

Red,Green,Blue就是普通的对象,不过是字母大写了而已,使用Color.Red照样进行访问,每一个都是一个Value类型的对象,Color.ValueWeekday.Value是不一样的类型。常用的方法有values来获取名称,也可以使用id来获取名称。在Enumeration中定义了一个HashMapInt -> Value类型,Value中有两个成员,一个是Int用来表示id,一个是String表示的Name,如果出现Value("readableName")Name中就会保存readableName


货币实例

type Currency <: AbstractCurrencydef make(amount: Long): Currency ...
上一篇 下一篇

猜你喜欢

热点阅读