Chapter 20 《Abstract Members》

  • 如果类或者特质的某个成员在当前类中没有完成的定义,则这个成员就是抽象的。抽象成员的本意是强制子类进行实现。Scala相对Java泛化了抽象字段的意义,存在四种抽象成员,valvar,方法和类型。

抽象成员概述

trait Abstract {
type T
def transform(x: T): T
val initial: T
var current: T
}
    1. 声明了四种抽象成员,抽象类型,抽象方法,抽象val和抽象var
    1. Scala中,抽象的类和特质不叫抽象类型,抽象类型永远是类或者特质的一个成员。
    1. 使用关键字type可以为真名冗长或者含义不明显的类型定义一个别名;另一个主要用于是声明子类必须定义的抽象类型。
    1. 抽象val限制了它的具体实现,因为def实现的方法可能每次的返回值都不一样,因此抽象val的实现只能是val
    1. 抽象var,抽象var在类中或者特质中定义的时候也会自动生成对应的def namedef name_=方法

初始化抽象val

    1. 抽象val有时会承担超类参数的职能,允许在子类中提供那些在超类中缺失的细节。对于特质来说是很重要的,因为在特质中并没有类参数,因此通常来说特质的参数化是通过子类中实现抽象val实现的。
trait RationalTrait {
val numerArg: Int
val denomArg: Int
}
  • 2.一种实例化方法:
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字段。

    1. 预初始化字段是指在超类被调用之前初始化子类的字段,例如:
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
^```
    1. 另外一种解决方法是使用懒加载的val,使用预初始化字段可以精确模拟类构造方法的入参初始化行为,如果希望系统自己能搞定应有的初始化顺序时,将val定义为惰性的即可,在val上加上lazy,右侧的初始化表达式只会在val第一次被使用时求值。将接口中涉及到子类初始化的字段全部设置成为lazy,得到以下接口:
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

    1. lazy val的初始化顺序和其在代码中的定义顺序并没有任何关系,因为其值会按需初始化。lazy val可以避免程序员一直组织val的初始化顺序保证在使用时已经正确定义。但这种优势只有在val的初始化是纯函数式的,没有副作用,对函数式对象而言初始化顺序并不重要,最后只要初始化完成即可。但是指令式的代码中,lazy val的初始化顺序变得难以跟踪,所以是函数式对象的完美补充。

抽象类型

  • type T是在类继承关系下游中被定义的类型,在Scala中,对override中的类型检查严格,主要还是继承树上C-F-C1的问题,如果override中参数可以是F的,则具体的子类可以传不配套的C1类型,导致出现牛吃鱼的问题。override中参数类型必须是严格匹配的,不允许使用子类覆盖父类这种写法。
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类型是不对的。


路径依赖类型

    1. bessy.SuitableFood这样的type称为路径依赖类型,路径指的是对对象的引用。
    1. Scala中也支持内部类,内部类的寻址是通过Outer#Inner这样的语法,内部类的类型和外部类对象有关,new Outer.Inner的类型和new Outer.Inner的类型是不一样的,但都是Outer#Inner的子类。和Java一样,Scala内部类中会保留一个到外部类实例的引用。允许内部类访问外部类的成员,因此在实例化内部类的时候必须以某种方式给出外部类的实例。注意,直接new Outer#Inner是不行的,因为没有Outer的实例。

改良类型

  • 结构子类型,如果某个类型除了成员之外没有更多的信息可以使用结构子类型。例如,如果想定义一个食草动物的列表,一种选择是定义AnimalThatEatGrass特质,并在对应的类上混入,另一种方式是使用改良类型,使用基类Animal,再加上一系列使用花括号括起来的成员即可。花括号中的成员进一步指定(改良)了基类中的成员类型。
Animal { type SuitableFood = Grass }
val animals = List[Animal { type SuitableFood = Grass }](new Cow)

枚举

  • Scala中的枚举是使用scala.Enumeration来表示的。
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 ...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352