Scala语法详解之类与对象_Android, Python及开发编程讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Android, Python及开发编程讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3196 | 回复: 0   主题: Scala语法详解之类与对象        下一篇 
wei.wang
注册用户
等级:少校
经验:1001
发帖:87
精华:0
注册:2013-8-29
状态:离线
发送短消息息给wei.wang 加好友    发送短消息息给wei.wang 发消息
发表于: IP:您无权察看 2013-9-6 10:50:54 | [全部帖] [楼主帖] 楼主

5. 类与对象

语法:

TmplDef ::= [„case‟] „class‟ ClassDef
| [„case‟] „object‟ ObjectDef
| „trait‟ TraitDef


类(§5.3)与对象(§5.4)都用模板来定义。

5.1.

语法:

模板

::= [EarlyDefs] ClassParents [TemplateBody]
::= [EarlyDefs] TraitParents [TemplateBody]
::= Constr {„with‟ AnnotType}
::= AnnotType {„with‟ AnnotType}
::= [nl] „{‟[SelfType] TemplateStat{semi TemplateStat}„}‟
::= id [„:‟ Type] „=>‟
| this „:‟ Type „=>‟
ClassTemplate
TraitTemplate
ClassParents
TraitParents
TemplateBody
SelfType


    模板定义了对象的特征或对象的类或单个对象的类型签名,行为和初始状态。模板是

实例创建表达式,类定义和对象定义的一部分。模板 sc with mt1 with ... with

mtn {stats}包括一个构造器调用 sc,定义了模板的超类;以及特征引用 mt1,...,mtn

(n>=0),定义了模板的特征;和一个语句序列 stats,包括初始化代码和模板额外的成

员定义。

     每个特征引用 mti 必须表示一个特征(§5.3.3),作为对比,超类构造器 sc 一般指向

一个类而不是特征。可以写出由特征引用开始的一系列的父类,比如 mt1 with ...

with mtn。在这种情况下父类列表被自动扩展以包括 mt1 的超类,并作为第一个父类型。

新的超类应当有至少一个无参数构造器。在以后内容中,我们将总是假定该自动扩展已经

被执行,因此模板的第一个父类是一个正规的超类构造器,而不是一个特征引用。

     每个类的父类列表也总是自动扩展至 scala.ScalaObject 特征做为最后一个混入

类型。例如:

sc with mt1 with ... with mtn {stats}


将变为

mt1 with ... with mtn {stats} with ScalaObject {stats}
39


模板

     模板的父类列表必须格式正确。也就是由超类构造器 sc 指示的类必须是所有特征

mt1,...,mtn 的超类的子类。换句话说,模板继承的非特征类在继承层级中构成了一个链,

其起始为模板的超类。

     模板的超类型的最低要求是类类型或复合类型(§3.2.7)由其所有的父类类型构成。

     语句序列 stats 包括成员定义,定义了新成员或覆盖父类中的成员。如果模板构成了

抽象类或特征的定义,语句部分 stats 还可以包括抽象成员的声明。如果模板构成了实体

类的定义,stats 仍可以包括抽象类型成员的声明,但是不能包括抽象术语成员。更进一

步,stats 还可以包括表达式;这些将以他们给定的顺序作为模板的初始化步骤的一部分

来执行。

     模板语句的序列可以有一个正式的参数定义和一个箭头作为前缀,例如 x=>或 x:T

=>。如果给出了一个正式的参数,这个参数在模板体中可用作引用 this 的别名。如果给

出了正式的参数的类型 T,这个定义就会以下面的方式影响类或对象的自类型 S:设 C 是

定义了模板的类、特征或对象的类型,如果给定正式的自参数类型 T,S 就是 T 和 C 的最

大下界。如果没有给出 T,S 就是 C。在模板内,this 的类型就会被假定为 S。

     类或者对象的自类型必须与模板 t 继承的所有类的自类型一致。

     自类型标注的第二种形式是 this: S=>。它规定了 this 的类型 S,但并没有为其引

入别名。

示例 5.1.1 考虑以下的类定义

class Base extends Object{}
trait Mixin extends Base{}
object O extends Mixin{}


这种情况下,O 的定义可扩展为

object O extends Base with Mixin{}


继承自 Java 类型 模板可以有一个 Java 类作为其超类,或者 Java 接口作为其混入。

模板求值 考虑模板 sc with mt1 with mtn {stats}。如果这是特征(§5.3.3)的一

个模板,那么其混入求值由语句序列 stats 的求值构成。

如果这不是一个特征的模板,那么其求值包括以下步骤:

l 首先,对超类的构造器 sc(§5.1.1)求值

l 然后,模板线性化中的所有基类,直到有 sc 表示的模板的超类将会做混入求值。

    混入求值的顺序是线性化中出现顺序的反序,比如紧挨 sc 前的类会被第一个求

    值。

l 最后对语句序列 stats 求值

5.1.1. 构造器调用

语法:

Constr
::= AnnotType {„(‟ [Exprs [„,‟]] „)‟}


   构造器调用定义了类型,成员以及由实例创建表达式或由类或对象定义继承的对象定

义创建的对象的初始状态。构造器调用是一个函数应用

x.c[targs](args1)...(argsn),x 是一个稳定的标识符(§3.1),c 是一个指向类或

40


类与对象

定义别名类型的类型名,targs 是一个类型参量列表,args1,...,argsn 是参量列表,

与该类的某个构造器的参数匹配。

前缀‟x.‟可以省略。类型参数列表只在类 c 需要类型参数时才给出。即使这样这也可

以在使用本地类型推断(§6.25.4)可以合成参数列表时忽略。如果没有显式的给出参量,

就会默认给一个空参量列表()。

    构造器调用 x.c[targs](args1)...(argsn)的执行包括以下几个步骤:

l 首先对前缀 x 求值

l 然后参量 args1,...,argsn 按照从左至右的顺序求值。

l 最后,对 c 指向的类的模板求值,初始化正在被创建的内容。

5.1.2. 类的线性化

通过类 C 可达的直接继承关系的传递闭包可达的类称为 C 的基类。由于混入的关系,

基类的继承关系基本上构成一个直接非循环图。这个图的线性化如下定义:

定义 5.1.2 设类 C 有模板 C1 with ... with Cn { stats }。C 的线性化 L(C)定义

如下:

L(C) = C, L(Cn) ... L(C1)


这里 表示串联,算符右侧的元素替换算符左侧标识的元素

{a,A} B


= a,(A B) 如果 a¢B

如果 a B

= (A B)


示例 5.1.3 考虑以下的类定义

abstract class AbsIterator extends AnyRef { ... }
trait RichIterator extends AbsIterator { ... }
class StringIterator extends AbsIterator { ... }
class Iter extends StringIterator with RichIterator { ... }


那么类 Iter 的线性化就是:

{ Iter, RichIterator, StringIterator, AbsIterator, ScalaObject, AnyRef,
Any }


特征 ScalaObject 出现在列表里是因为每个 Scala 类(§5.1)都会添加它作为最后

的混入。

注意一个类的线性化优化了继承关系:如果 C 是 D 的子类,那么 C 就会在任何 C 和 D

同时出现的线性化中出现在 D 前面。定义 5.1.2 也满足一个类的线性化总是包括其直接

超类的线性化作为其后缀这个性质。例如,StringIterator 的线性化就是:

{ StringIterator, AbsIterator, ScalaObject, AnyRef, Any }


   这 就 是 其 子 类 Iter 的 线 性 化 的 后 缀 。 对 混 入 的 线 性 化 却 不 是 这 样 。 例 如 ,

RichIterator 的线性化是:

{ RichIterator, AbsIterator, ScalaObject, AnyRef, Any}


这并不是 Iter 线性化的后缀。

41


模板

5.1.3. 类成员

     由模板 C1 with ... with Cn { stats }定义的类 C 可以在语句序列 stats 中

定义成员和继承所有父类的成员。Scala 采取了 Java 和 C#的方法静态重载的方便之处。

因此一个类可以定义和/或集成多个同名方法。要确定类 C 定义的成员是否覆盖了父类的

成员,或 C 中两个同时存在的重载的变量,Scala 使用了以下的成员匹配定义:

定义 5.1.4 成员定义 M 与成员定义 M‟,匹配的条件是:首先他们绑定了同样的名称,然

后符合下面中的一条:

     1. M 和 M‟不是方法定义

     2. M 和 M‟定义了同态的方法并具有等价的参数类型

     3. M 定义了一个无参数方法,M‟定义了一个具有空参数列表的方法,或反之亦然。

     4. M 和 M‟定义了多态的方法,具有同样数目的参数类型 , ‟和同样数目的类型参

     数 , ‟,并且 ‟=[ ‟/ ] 。

     成员定义有两类:实体定义与抽象定义。类 C 的成员要么直接定义(例如出现在 C 的

语句序列 stats 中)或继承。有两条规则来确定类的成员集合,每个分类一条:

定义 5.1.5 类 C 的实体成员是某些类 Ci L(C)中的实体定义 M,除非在前置的类

Cj L(C)(j<i)中有一个与 M 匹配的直接定义的实体成员 M‟。

     类 C 的抽象成员是某些类 Ci L(C)中任意抽象定义的 M,除非 C 已经包括一个与 M

匹配的实体成员 M‟,或者在前置类 Cj L(C)且 j<i 中已经有一个与 M 匹配的直接定义的

抽象成员 M‟。

     该定义也确定了类 C 及其父类(§5.1.4)中匹配的成员的重载关系。第一,实体定义

总是覆盖抽象定义。第二,如果 M 和 M‟都是实体的或抽象的,只有 M 定义的类在(C 的线

性化中)M‟所在类的前面出现时,M 才会重载 M‟。

     一个模板定义了两个匹配的成员将会导致错误。一个模板包括两个同名且具有同样擦

除(§3.7)类型的成员(直接定义或继承)也将会导致错误。

示例 5.1.6 考虑以下特征定义

trait A { def f: Int }
trait B extends A{def f:Int = 1;def g:Int = 2;def h:Int=3}
trait C extends A{override def f:Int = 4; def g:Int}
trait D extends B with C{def h:Int}


  特征 D 有一个直接定义的抽象成员 h。它从特征 C 继承了成员 f,从特征 B 继承了成

员 g。

5.1.4. 覆盖

类 C 的成员 M 与(§5.1.3)中定义的 C 的基类的非私有成员 M‟一致可定义为覆盖该

成员。在此情况下覆盖成员 M 的绑定必须包含(§3.5.2)被覆盖的成员 M‟的绑定。另外以

下限制应用于 M 和 M‟的修饰符:

l M‟不能标记为 final。

l M 不能是 private(§5.2)。

l 如果 M 在某些封闭类或包 C 中标记为 private[C],那么 M‟必须在类或包 C‟中

     标记为 private[C‟],且 C‟等于 C 或 C‟包含于 C。

l 如果 M 标记为 protected,那么 M‟也必须是 protected。

42


类与对象

     如果 M‟不是一个抽象成员,那么 M 必须标记为 override

     如果 M‟在 C 中是不完整(§5.2)的,那么 M 必须标记为 abstract override

     如果 M 和 M‟都是实体值定义,那么他们必须都标记为 lazy 或者都不标记为

lazy。


   对于无参数方法有个特例。如果一个无参数方法定义为 def f: T = ...或者 def

f = ...覆盖了类型()T‟中的一个空参数列表方法,那么 f 也被假定为具有一个空参数

列表。

示例 5.1.7 考虑以下定义

trait Root { type T <: Root}
trait A extends Root { type T <: A}
trait B extends Root { type T <: B}
trait C extends A with B
l
l
l


  那么类 C 的定义是错误的,因为 C 中 T 的绑定是 type T <: B,不能包含绑定 A 中

的 type T <: A。该问题可通过在类 C 中添加 T 的覆盖定义来解决。

class C extends A with B { type T <: C}


5.1.5. 继承闭包

设 C 为类类型。C 的继承闭包就是以下类型的最小集合 υ:

l 如果 T 在 υ 中,那么语法上构成 T 的每个类型 T‟也在 υ 中。

l 如果 T 是 υ 中的一个类类型,那么 T 的所有父类(§5.1)也在 υ 中。

如果类类型的继承闭包包含无穷个类型将会导致静态错误。(该限制对于使子类型可

推断[KP07]是必要的)。

5.1.6. 前置定义

语法:

EarlyDefs
EarlyDef
::= „{‟ [EarlyDef {semi EarlyDef}] „}‟ „with‟
::= {Annotation} {Modifier} PatVarDef


模板开头可以是前置字段定义子句,该子句在子类型构造器被调用之前定义了字段的

值。在以下模板中

{ val p1: T1 = e1
...
val pn: Tn = en
} with sc with mt1 with mtn {stats}


初始模式定义 p1,...,pn 被称为前置定义。他们定义了构成模板的字段。每个前置定

义必须定义至少一个变量。

前置定义在模板被定义与赋类型参数以及在此之前的任意前置定义之前做类型检查与

求值,参数可以是类的任意类型参数。在前置定义中在右侧任何对 this 的引用指模板之

43


修饰符

外的 this 标识符。因此,前置定义不可能引用模板创建的对象,或者引用其字段与方法,

除了同一段落中前面的前置定义之外。再者,对前面的前置定义的引用总是引用那里定义

的值,并不牵涉到覆盖的定义。

换句话说,前置定义的代码块以包含一些值定义的局部代码块的形式求值。

前置定义在模板的父类构造器被调用之前以他们被定义的顺序求值。

示例 5.1.8 前置定义在特征中特别有用,它们没有通常的构造器参数。例如:

trait Greeting {

val name: String

val msg = “How are you, “+name

}

class C extends {

val name = “Bob”

} with Greeting{

println(msg)

}

在以上代码中,字段 name 在 Greeting 的构造器之前被初始化。类 Greeting 中的

字段 msg 被正确初始化为”How are you, Bob”。

如果 name 不是在 C‟的类主体中被初始化,而是在 Greeting 的构造器之后初始化。

在此情况下,msg 会被初始化为”How are you, <null>”。

5.2.

语法:

修饰符

::= LocalModifier
| AccessModifier
| „override‟
Modifier
LocalModifier
::= „abstract‟
| „final‟
| „sealed‟
| „implicit‟
| „lazy‟
AccessModifier ::= („private‟ | „protected‟) [AccessQualifier]
AccesQualifier ::= „[‟ (id | „this‟) „]‟


成员定义前的修饰符会影响其标定的标识符的可见性及使用。如果给出了多个修饰符,

其顺序没有关系,但是同一个修饰符不能重复出现。重复定义前的修饰符将应用于所有定

义上面。控制修饰符的有效性与含义的规则如下:

l private 修饰符可以应用在模板的任何定义与声明上。这些成员只能被直接封闭

    的模板和其伴随模块及伴随类访问(示例 5.4.1)。他们不能被子类继承,也不能

    覆盖父类中的定义。

    此修饰符可由一个标识符 C 限定(如 private[C]),表示该类或包包含该定义。

    由该标识符标识的成员只能由包 C 或类 C 以及他们的伴随模块(§5.4)来访问。

44

类与对象

l
l
l
l
l


这些成员也仅能由 C 内的模板访问。

限定的一个特殊形式是 private[this]。由该标识符标记的成员 M 只能从该成

员定义的对象内访问。也就是选择 p.M 只有在前缀是 this 和包含该引用的类 O

的 O.this 时才合法。这也就是没有加限定的 private 的应用。

标记为没有限定的 private 的成员称为类私有,标记为 private[this]的成

员称为对象私有。不管是类私有还是对象私有都可以称为私有成员,但是

private[C]不是,C 是一个标识符,在后者该成员称为限定私有。

类私有或对象私有成员不能是抽象的,并且不能再由 protected,final 或

override 修饰符限定。

protected 标识符应用到类成员定义上。类的保护成员可以从以下位置访问:

- 定义的类模板内

- 所有以定义的类为基类的模板

- 任何这些类的伴随模块

     protected 修饰符可以由一个标识符 C 来限定(例 protected[C]),C 必

须是一个包含该定义的类或者包。由该修饰符标记的成员可以被包或者类 C 内的

所有代码及伴随模块(§5.4)访问。

     一个 protected 标识符 x 可在选择 r.x 作为成员名称的条件是:

- 访问是在定义该成员的模板内;如果给出了限定 C 的话,就是在包或者类 C

     以及其伴随模块内,或者:

- r 是保留字 this 和 super 中的一个,或者:

- r 的类型与包含访问的类的类型实例一致

     限定的一个特殊形式是 protected[this]。被此修饰符标记的成员 M 只能

从其定义的对象内访问。也就是选择 p.M 只有在前缀是 this 或者 O.this 的时

候才合法(类 O 包含该引用)。这也就是未加限定的 protected 应用的方式。

override 修饰符应用于类成员定义或声明。对于那些覆盖了父类中某些实体成

员定义的成员定义与声明,该修饰符是必须的。如果给出了 override 修饰符,

那么应当至少有一个被覆盖的成员定义或声明(可以是实体的或者抽象的)

当 override 和 abstract 一起出现时具有显著不同的意义。该修饰符组合仅

用于特征的值成员。标记为 abstract override 的成员必须覆盖至少一个其

他成员,所有被其覆盖的成员必须是不完整的。

成员 M 是不完整的条件是:M 是抽象的(例如由一个声明定义)或者标记为

abstract 和 override,这样即使是被 M 覆盖的成员也成为不完整的。

注意修饰符组合 abstract override 并不影响一个成员是不是实体或者抽象

的概念。如果一个成员仅给出了一个声明,那么它就是抽象的;如果给出了完整

定义,那么它就是实体的。

abstract 修饰符可用于类定义。但没必要用于特征。对于具有不完整成员的类

来说是必须的。抽象类不能通过构造器调用初始化(§6.10),除非后跟覆盖了类

中所有不完整成员的混入和/或修饰体。只有抽象类和特征可以有抽象术语成员。

正如前文所述,abstract 修饰符可以和 override 连用,应用于类成员定义。

final 修饰符应用于类成员定义和类定义。标记为 final 的类成员定义不能在

子类中被覆盖。标记为 final 的类不能被模板继承。final 对于对象定义是多

余的。标记为 final 的类或对象的成员隐含定义为 final 的,所以对它们来说

final 修饰符也是多余的。final 修饰符不能修饰不完整成员,并且在修饰符列

表中不能与 private 或 sealed 组合。

45


类定义

     sealed 修饰符应用于类定义。标记为 sealed 的类不能被直接继承,除非继承

     的模板和该类定义于同一源文件。然而 sealed 的类的子类可以在任何地方继承。

l lazy 修饰符应用于值定义。标记为 lazy 的值只在其第一次被访问(可能永远也

     不发生)时初始化。试图在值初始化时访问该值可能导致循环行为。如果在初始

     化时有异常被抛出,该值则被认为没有被初始化,随后的访问将会继续尝试对其

     右侧表达式求值。

示例 5.2.1 以下代码列举了限定私有的用法:

package outerpkg.innerpkg
class Outer {
      class Inner {
            private[Outer] def f()
            private[innerpkg] def g()
            private[outerpkg] def h()
      }
}
l


   在这里对方法 f 的访问可以出现在 OuterClass 的任何地方,但不能在其外面。对

方法 g 的访问可以出现在包 outerpkg.innerpkg 的任何地方,和 Java 中的包私有方

法类似。最后,对方法 h 的访问可以出现在包 outerpkg 的任何地方,包含其包括的所有

包。

示 例 5.2.2 阻 止 类 的 使 用 者 去 创 建 该 类 的 新 实 例 的 一 个 常 用 方 法 是 将 该 类 声 明 为

abstract 和 sealed

object m {
      abstract sealed class C (x: Int) {
      def nextC = new C(x + 1) {}
      }
val empty = new C(0){}
}


例如以上的代码用户只能通过调用 m.C 中的 nextC 方法来创建类 m.C 的实例。用户

无法直接创建类 m.C 的对象。以下两行是错误的:

new m.C(0) //*** 错误:C 是抽象的,不能初始化
new m.C(0){}
//*** 错误:从 sealed 类非法继承


也可以通过将主要构造器标记为 private 来达成这一目的(参见示例 5.3.2)。

5.3.

语法:

类定义

::= „class‟ ClassDef
::= id [TypeParamClauses] {Annotation}
::= {ClassParamClause}
[[nl] „(‟ implicit ClassParams „)‟]
::= [nl] „(‟ [ClassParams] „)‟
TmplDef
ClassDef
ClassParamClauses
ClassParamClause
46


类与对象

ClassParams
ClassParam
ClassTemplateOpt
::= ClassParam {„,‟ ClassParam}
::= {Annotation} [{Modifier} („val‟ | „var‟)]
Id [„:‟ ParamType]
::= „extends‟ ClassTemplate
| [[„extends‟] TemplateBody]


类定义最常见的形式是

class c[tps] as m(ps1)...(psn) extends t
(n>=0).


此处:

     C 是要定义的类的名称

     tps 是要定义的类的类型参数的非空列表。类型参数的作用域是整个类定义,包

括类型参数段自身。用同一个名称来定义两个类型参数是非法的。类型参数段[tps]

可以没有。具有类型参数段的类称为多态的,否则称为单态的。

     as 是一个可为空的标注(§11) 序列。如果给出了标注,他们将应用于类的主构

造器。

     m 是访问修饰符(§5.2),比如 private 或者 protected,可以有一个限定。

如果给出了这样一个访问修饰符,它将应用于类的主构造器。

     (ps1)...(psn)是类的主构造器的正式值参数子句。正式值参数的作用域包含模

板 t。然而一个正式值参数并不是任何父类或类型模板 t 成员的一部分。用同一个名

称来定义两个正式值参数是非法的。如果没有给出正式参数段,则会假定有一个空的

参数段()。

     如果正式参数声明 x:T 前面有 val 或者 var 关键字,针对该参数的一个访问器

(getter)定义(§4.2)将会被自动加入类中。getter 引入了类 c 的值成员 x,其定

义是该参数的别名。如果引入关键字是 var,一个 setter 访问器 x_=(§4.2)也会

被自动加入到类中。调用该 setter x_=(e)将会将参数的值变为 e 的求值结果。正

式参数声明可以包含修饰符,并将会自动传递给访问器定义。一个有 val 或者 var

前缀的正式参数不能同时成为叫名参数(§4.6.1)。

     t 是一个模板(§5.1),具有以下形式:

sc with mt1 with ... with mtm ( stats )
(m>=0)


   该模板定义了类的对象的基类,行为和初始状态。extends 子句 extends sc

with mt1 ... with mtm 可以被忽略,默认为 extends scala.AnyRef。类体

{stats}也可以没有,默认为空{}

这个类定义定义了一个类型 c[tps]和一个构造器,当该构造器应用于与类型 ps 一致

的参数时将会通过对模板 t 求值来创建类型 c[tps]的实例。

示例 5.3.1 以下例子展示了类 C 的 val 和 var 参数

class C(x: Int, val y: String, var z: List[String])
var c = new C(1, “abc”, List())
c.z = c.y :: c.z


示例 5.3.2 下面的类只能从其伴随模块中创建

object Sensitive {
47


类定义

def makeSensitive(credentials: Certificate): Sensitive =
if (credentials == Admin) new Sensitive()
else throw new SecurityViolationException
}
class Sensitive private (){
      ...
}


5.3.1. 构造器定义

语法:

FunDef
ConstrExpr
ConstrBlock
::= „this‟ ParamClause ParamClauses
(„=‟ ConstrExpr | [nl] ConstrBlock)
::= SelfInvocation
| ConstrBlock
::= „{‟ SelfInvocation {semi BlockStat} „}‟
SelfInvocation ::= „this‟ ArgumentExprs {ArgumentExprs}


   一个类可以有除主构造器外的构造器。这些由形如 def this(ps1)...(psn) = e

之类的构造器定义所定义。这样的定义在类内引入了额外的构造器,并具有以正式参数列

表 ps1,...,psn 形式的参数,其求值由构造器表达式 e 所定义。每个正式参数的作用域

是构造器表达式 e。一个构造器表达式可以是一个构造器自调用 this(args1) ...

(argsn)或者一个以构造器自调用开始的代码块。构造器自调用必须创建一个类的通用实

例。例如,如果问题中的类具有名称 C 和类型参数[tps],那么构造器自调用必须产生一

个 C[tps]的实例;初始化正式类型参数是不允许的。

     一个构造器定义中的签名及构造器自调用是有类型检查的,并在类内产生作用域的地

方求值,可以加该类的任何类型参数以及该模板的任何前置定义(§5.1.6)。构造器的其

他部分会被类型检查并以当前类中一个函数体的形式求值。

     如果类 C 有辅助构造器,这些构造器与 C 的主构造器(§5.3)构成了重载的构造器定

义。重载解析(§6.25.3)的通常规则应用于 C 的构造器调用,包括构造器表达式中的构造

器自调用。然而,不同于其他方法,构造器从不继承。为了防止构造器调用的无限循环,

限制了每个构造器自调用只能引用它前面定义的构造器(例如它只能引用前面的辅助构造

器或类的主构造器)

示例 5.3.3 考虑以下类定义:

class LinkedList[A]() {
      var head = _
      var tail = null
      def isEmpty = tail != null
def this(head: A) = { this(); this.head = head }
def this(head: A, tail: List[A]) = { this(head); this.tail = tail}
}


这里定义了类 LinkedList 和三个构造器。第二个构造器创建了一个单值列表,第三

个构造器创建了一个给出了 head 和 tail 的列表。

48


类与对象

5.3.2. Case 类

语法

TmplDef ::= „case‟ „class‟ ClassDef


如果一个类定义有 case 前缀,那么该类就被称为 case 类

case 类中第一个参数段中的正式参数称为元素,对它们将作特殊处理。首先,该参

数的值可以扩展为构造器模式的一个字段。其次,该参数默认添加 val 前缀,除非该参数

已经有 val 或 var 修饰符。然后会针对该参数生成一个访问定义(§5.3)。

case 类定义 c[tps](ps1)...(psn)有类参数 tps 和值参数 ps,会自动生成一个

扩展对象(§8.1.7),定义如下:

object c {
      def apply[tps](ps1)...(psn): c[tps] = new c[Ts](xs1)...(xsn)
      def unapply[tps](x: c[tps]) = scala.Some(x.xs11,...,x.xs1k)
}


   这里,Ts 是类型参数段 tps 中定义的类型向量,每个 xsi 表示参数段 psi 中的参数

名称,xs11,...,xs1k 表示第一个参数段 xs1 中所有的参数名。如果类中没有类型参数段,

那么 apply 和 unapply 方法也就没有了。如果类 c 是 abstract 的,apply 的定义会

被忽略。如果 c 的第一个参数段 ps1 以一个(§4.6.2)中的重复参数结尾,unapply 方法

的名称会改为 unapplySeq。如果已经存在伴随对象 c,则不会创建新的对象,但是

apply 和 unapply 方法会添加进现有对象中。

     每个 case 类都自动重载类 scala.AnyRef(§12.1)中的一些方法定义,除非 case

类本身已经给出了该方法的定义或在 case 类的某些基类中已经有与 AnyRef 中的方法不

同的实体方法定义。特别是:

     equals: (Any)Boolean 方法是结构相等的,两个实例相等的条件是他们都属于问

     题中的 case 类,且他们具有相同的构造器参数。

     hashCode: Int 方法计算一个哈希码。如果数据结构成员的 hashCode 方法产生对

     应相等的哈希值,那么 case 类的 hashCode 产生的值也要相等。

     toString: String 方法返回一个包含类名和其元素的字符串表示。

示例 5.3.4 以下是 lambda 演算的抽象语法定义

class Expr
case class Var
case class Apply
(x: String)
(f: Expr, e: Expr)
extends Expr
extends Expr
extends Expr
case class Lambda (x: String, e: Expr)


此处定义了一个类 Expr 和 case 类 Var, Apply 和 Lambda。一个 lambda 表达式

的传值参数计算器可以写为如下方式:

type Env = String => Value
case class Value(e: Expr, env: Env)
def eval(e: Expr, env: Env): Value = e match {
case Var(x) =>
env(x)
49


类定义

case Apply(f, g) =>
val Value(Lambda (x, e1), env1) = eval(f, env)
val v = eval(g, env)
eval (e1, (y => if (y == x) v else env1(y)))
case Lambda(_,_) =>
Value(e, env)
}


可以通过在程序的其他地方扩展类型 Expr 来定义更多的 case 类,例如:

case class Number(x: Int) extends Expr


   可以通过将基类 Expr 标记为 sealed 来移除扩展性;在此情况下,所有直接扩展

Expr 的类必须与 Expr 在同一源文件中

5.3.3. 特征

语法:

TmplDef
TraitDef
TraitTemplateOpt
::= „trait‟ TraitDef
::= id [TypeParamClause] TraitTemplateOpt
::= „extends‟ TraitTemplate
| [[„extends‟] TemplateBody]


  特征是那些要以混入的形式加入到其他类中的类。与通常的类不同,特征不能有构造

器参数。且也不能有构造器参数传递给其父类。这些都是没必要的,因为特征在父类初始

化后才进行初始化。

     假定特征 D 定义了类型 C 的实例 x 的某些特点(例如 D 是 C 的一个基类)。那么 x 中

D 的实际超类型是 L(C)中超越 D 的所有基类的复合类型。实际超类型给出了在特征中解

析 super 的上下文(§6.5)。要注意到实际超类型依赖于特征所添加进的混入组合,当定

义特征时是无法知道的。

     如果 D 不是特征,那么它的实际超类型就是其最小合适超类型(实际在定义时可知)

示例 5.3.5 以下特征定义了与某些类型的对象可以比较的属性。包括一个抽象方法<和其

他比较算符<=,>和>=的默认实现。

trait Comparable[T <: Comparable[T]] { self: T =>
      def < (that: T): Boolean
      def <=(that: T): Boolean = this < that || this == that
      def > (that: T): Boolean = that < this
      def >=(that: T): Boolean = that <= this
}


示例 5.3.6 考虑抽象类 Table 实现了由键类型 A 到值类型 B 的映射。该类有一个方法

set 来将一个新的键值对放入表中,和方法 get 来返回与给定键值匹配的可选值。最后,

有和 get 方法类似的方法 apply,只是如果表中没有给定键的定义该方法将会返回一个给

定的默认值。该类实现如下:

abstract class Table[A, B](defaultValue: B) {
50


类与对象

def get(key: A): Option[B]
def set(key: A, value: B)
def apply(key: A) = get(key) match {
      case Some(value) => value
      case None => defaultValue
}
}


以下是 Table 类的实际定义。

class ListTable[A, B](defaultValue: B) extends Table[A,
B](defaultValue){
      private var elems: List[(A,B)]
      def get(key: A) = elems.find(._1.==(key)).map(._2)
def set(key: A, value: B) = { elems = (key, value) :: elems }
}


以下是一个特征来防止对父类中 get 和 set 操作的并发访问:

trait SynchronizedTable[A, B] extends Table[A, B] {
      abstract override def get(key: A): B =
synchronized { super.get(key) }
      abstract override def set(key: A, value: B) =
synchronized { super.set(key, value) }
}


   注意 SychronizedTable 并没有传递给父类 Table 参数,即使 Table 定义了正式

参数。同样注意到 SynchronizedTable 的 get 和 set 方法中对 super 的调用静态地

引 用 了 父 类 Table 中 的 抽 象 方 法 。 这 是 合 法 的 , 因 为 该 方 法 标 记 为 abstract

override (§5.2)。


   最后,以下混入组合创建了一个同步的列表,以字符串作为键,以整数作为值,并定

义 0 为缺省值。

object MyTable extends ListTable[String, Int](0) with SynchronizedTable


   对象 MyTable 从 SynchronizedTable 中继承了 get 和 set 方法。这些方法中对

super 的调用与对应的 ListTable 中的对应方法重新绑定,实际就是 MyTable 中

SynchronizedTable 中的实际超类型。

5.4.

语法:

对象定义

::= id ClassTemplate
ObjectDef


   对象定义定义了一个新类的单个对象。最常用的形式是 object m extends t。这

里 m 是要定义的对象的名称,t 是一个具有以下形式的模板(§5.1)

sc with mt1 with ... with mtn { stats }
51


对象定义

     此处定义了 m 的基类,行为以及初始状态。extends 子句 extends sc with mt1

with ... with mtn 可忽略,默认是 extends scala.AnyRef。类体 {stats}也可

被忽略,默认为空{}。

     对象定义定义了与模板 t 一致的单个对象(或:模块)。它大概等同于以下的三个定义,

定义了一个类并按需创建了该类的单个对象。

final class m$cls extends t
private var m$instance = null
final def m = {
      if (m$instance == null) m$instance = new m$cls
      m$instance
}


   如果该定义是代码块的一部分则这里的 final 修饰符可忽略。名称 m$cls 和

m$instance 不可从用户程序中访问。

     注意到对象定义的值是懒加载的。构造器 new m$cls 并不是在对象定义时求值,而

是在程序执行是 m 第一次被去引用时(可能永远也不会发生)。试图再次对构造器求值来重

新去引用将导致死循环或运行时错误。

     然而以上讨论并不能应用于顶级对象。不能这样的原因是变量和方法定义不能出现在

顶级。而顶级对象将被翻译为静态字段。

示例 5.4.1 Scala 中的类没有静态成员;然而可以通过对象定义来达到等价的效果,例

如:

abstract class Point {
      val x: Double
      val y: Double
      def isOrigin = (x == 0.0 && y == 0.0)
}
object Point {
val origin = new Point() { val x = 0.0; val y = 0.0 }
}


  这里定义了一个类 Point 和一个包含成员 origin 的对象 Point。注意两次使用名

称 Point 是合法的,因为类定义在类型命名空间中定义了名称 Point,而对象定义在术

语命名空间中定义了 Point

     Scala 编译器在解释一个具有静态成员的 Java 类时使用了这种技术。这样的一个类

C 可以在概念上认为是包括所有的 C 的实例成员的一个 Scala 类,和包括所有 C 的静态成

员的一个 Scala 对象的一对组合。

     通常来讲,一个类的伴随模块是和类具有同样名称的一个对象,并定义在同样的作用

域和编译单元中。同样地,这个类可以称作该模块的伴随类。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论