Simple Scala: Object System

Parameterized Types: Variance Under Inheritance

Java和Scala在类型化参数(泛型)上最大的不同是variance under inheritance(继承下的变型)是怎么工作的.

比如一个方法接收一个List[AnyRef]类型的参数,你能给它传一个List[String]吗,或者说 List[String]是List[AnyRef]的子类型吗?如果是,则这种型变就叫做协变(covariance),因为容器的父子类型关系和类型参数的关系走势一致.

同时还有逆变(contravariant),即 X[String] 是 X[Any] 的父类型.

如果一个参数化类型就不是协变也不是逆变,则称为不可变型(invariant).

Java和scala均支持协变,逆变与不可变型,但是在Scala中,变型行为通过叫做变型注解(variance annotation)的方式在每个参数上作为类型声明的一部分被定义.

协变时使用符号 +, 逆变时使用符号 -,不变型时不用添加符号,下面是一些声明的例子:

class W[+A] {...}       // covariant 
class X[-A] {...}       // contravariant 
class Y[A] {...}        // invariant 
class Z[-A,B,+C] {...}  // mixed

类型变型注解和意义:

Scala Java Description
+T ? extends T 协变(eg:List[Tsub]是List[T]的子类型)
-T ? super T 逆变(eg:X[Tsup]是X[T]的子类型)
T T 不可变型(eg:不能将Y[Tsub]或Y[Tsup]适应为Y[T])

这时候再看List,实际上声明为List[+A],即List[String]是List[Any]的子类型,因此List在参数A是进行协变.

如果一个类型跟List一样只有一种变型,则说”List是协变的”,逆变也适用这种说法.

Functions Under the Hood

逆变最好的例子是一组FunctionN的特质,比如Scala.Function2,N是区间是0-22,根据函数的参数数量决定.Scala根据这些特质实现匿名函数.

我们已经见过匿名函数:

List(1,2,3,4)map(i=>i+3) // Result: List(4, 5, 6, 7)

函数表达式 i => i + 3 实质上是一个语法糖,编译器会把它转换成scala.Function1的匿名子类实例:

val f: Int => Int = new Function1[Int,Int] { 
    def apply(i: Int): Int = i + 3
}
List(1, 2, 3, 4) map (f) // Result: List(4, 5, 6, 7)

FunctionN特质是抽象的,因为其apply方法是抽象的.注意我们这里已经定义了他,表达式 i => i + 3 的作用就是用于定义apply方法.

现在回到逆变,如下是scala.Function2的声明:

trait Function2[-T1, -T2, +R] extends AnyRef

最后一个类型参数 +R 是返回值类型.它是协变的.前面的两个类型参数分别是函数参数.他们你逆变的,其他的FunctionN特质于此类似.

观察实例理解变型行为:

class CSuper                { def msuper() = println("CSuper") }    // 1
class C     extends CSuper  { def m() = println("C") }
class CSub  extends C       { def msub() = println("CSub") }

var f: C => C   = (c:C) =>newC                                      // 2
    f           = (c: CSuper) => new CSub                           // 3
    f           = (c: CSuper) =>new C                               // 4
    f           = (c:C) =>newCSub                                   // 5
    f           = (c: CSub) => new CSuper                           // 6 COMPILATION ERROR!
  1. 定义一组三重继承的类
  2. 把函数 f 定义为 var 以便能够重新赋值,所有进行赋值的函数实例必须是 C => C(Function1[C,C]),所有我们赋值的函数必须能够满足继承下的变型要求.
  3. 函数值 (c: CSuper) => new CSub 是有效的,因为参数C是逆变,所以CSuper是一个有效的替换,同时返回值是协变的,因此CSub可以有效替换C
  4. 和上面的一样,只是返回了一个 C
  5. 和上面一样,只是传入一个 C
  6. 错误,接收一个CSub参数是无效的,因为逆变. 返回值类型CSuper对于协变也是无效的

协变时可以使用子类进行替换,逆变时可以使用父类来替换.

函数变量 f 是一个 C => C (Function1[-C,+C])类型,第一次赋值跟签名准确吻合,即传入值与传出值均是C类型.

第二次赋值,(x:CSuper) => CSub,同样遵守了逆变参数和协变返回值,但是为什么这样就安全(可用)呢?

关键的洞擦力是 f 函数具体是如何使用的或者我们可以使用它完成什么功能.如果我们定义为 C => C,则所有有效的C类型值可以传入同时永远不会返回C类型意外任何类型的值.

但如果实际类型是 (x:CSuper) => CSub, 该函数就不仅仅只能接受C类型的值,它同时能够接收其父类型CSuper或它其他的子类型的值. 因此我们只会传入一个C的实例,而不会传入任何超出 f 函数允许范围之外的类型.而此时,f 的能力比他需要做的要宽大的多.

同样,如果他只返回CSub实例也是同样安全的,因为调用者既然能够处理C,当然能够处理其子类CSub.

上面的第6项同时违背了两条规则,我们观察一下如果我们真的允许这么做了会发生什么.

这个例子中,函数 f 只知道怎么处理CSub的实例,但是调用者以为任何C的实例都能够传入,因此运行时会报错,比如调用一些仅仅在CSub中定义的方法,而C中没有.同样,如果 f 能够返回一个CSuper,这同样会让调用者吃惊,因为返回值超出了预期的实例类型,而预期的是类型C的实例.

这就是为什么函数的参数必须逆变而返回值必须协变.

变型注解只能作用于类的类型参数,不能用作参数化方法,因为注解影响了子类型的行为.方法并不是类型化的.比如List.map方法的签名:

sealed abstract class List[+A] ... { // mixin traits omitted ...
    def map[B](f: A => B): List[B] = {...}
    ... 
}

B上并没有类型注解,如果强加一个则会编译器报错.

最后编译器会检查你变型注解中的无效用法,比如下面在函数定义中使用了错误的注解:

scala> trait MyFunction2[+T1, +T2, -R] { 
        | defapply(v1:T1,v2:T2):R=???
        |}
<console>:37: error: contravariant type R occurs in covariant position
in type (v1: T1, v2: T2)R of method apply
    def apply(v1:T1, v2:T2): R = ??? 
        ^
<console>:37: error: covariant type T1 occurs in contravariant position 
in type T1 of value v1
    def apply(v1:T1, v2:T2): R = ??? 
        ^
<console>:37: error: covariant type T2 occurs in contravariant position 
in type T2 of value v2
    def apply(v1:T1, v2:T2): R = ??? 
        ^

注意看到错误提示中,编译器强制要求函数参数逆变,返回值协变.

Variance of Mutable Types

目前为止我们讨论的参数化类型都是不可变的.那可变类型的变型行为又是怎样的呢.答案就是只允许 不可变型:

scala> class ContainerPlus[+A](var value: A)
<console>:34: error: covariant type A occurs in contravariant position 
in type A of value value_=
    class ContainerPlus[+A](var value: A) 
          ^
scala> class ContainerMinus[-A](var value: A)
<console>:34: error: contravariant type A occurs in covariant position 
in type => A of method value
    class ContainerMinus[-A](var value: A) 
          ^

可变字段的问题是,它的行为像是一个带有公共读写方法的私有字段,甚至是一个没有明显访问方法的共有字段.

def value_ = (newA: A): Unit+ 是编译器解释后的变量的setter签名,意思是,当我们写一个 myinstance.value = someA 这样的表达式时这个方法就会调用,因此注意第一条错误提示,我们在逆变的位置使用了协变类型A.

The Scala Type Hierarchy

参考Scala标准库类型继承图.

在分层结构的顶层是Any,它没有父类,有三个子类:

  1. AnyVal,类型值(value types)和类值(value classes)的父类
  2. AnyRef,所有引用类型的父类
  3. 通用特质

AnyVal有一个具体的子类叫做类型值(value types),有七种数字值类型:Byte, Char, Short, Int, Long, Float, Double,剩余的两个非数字类型是Unit和Boolean.

而其他所有类型都是引用类型(reference types),都是从AnyRef派生,与Java中的java.lang.Object同源.

Much Ado About Nothing (and Null)

处于在类型系统底层的Nothing和Null是两个特殊的类型,比较特殊的是Nothing是其他所有值类型的子类型,而Null是所有引用类型的子类型.

Null是很多编程语言中常用的概念,但是通常使用的时候不是定位为Null类型,只是用一个关键字null付给一个引用以表明该引用没有实际的值,其源码实现如下:

package scala
abstract final class Null extends AnyRef

为什么它能够同时是abstract和final呢? 该声明不允许有子类或创建自己的实例,运行时环境会提供一个实例.

Nothing在Java中没有对应的实现,Nothing的实现:

package scala
abstract final class Nothing extends Any

Nothing实际上继承自Any,因此Nothing是其他所有类型的子类型,所有的引用类型或值类型.

Products, Case Classes, and Tuples

case类混入了scala.Product特质,为实例提供一些操作字段的泛型方法,比如一个Person实例:

scala> case class Person(name: String, age: Int) 
defined class Person

scala> val p: Product = Person("Dean", 29)
p: Product = Person(Dean,29)    //The case class instance is assignable to a Product variable. 

scala> p.productArity
res0: Int = 2                   //The number of fields.

scala> p.productElement(0)
res1: Any = Dean                //Elements counted from zero.

scala> p.productElement(1)
res1: Any = 29

scala> p.productIterator foreach println 
Dean
29

使用泛型方法访问字段是非常有用的,他们的值类型被限定为Any而不是他们实际的类型.

Product同时有一些子类用于专门的用途,这些类型添加了一些方法用于选择字段,比如 Product2[+T1,+T2] 添加的方法:

package scala
trait Product2[+T1, +T2] extends Product {
    abstract def _1: T1 
    abstract def _2: T2 ...
}

这些方法会返回字段的实际类型,类型参数均为协变,因为特质ProductN只被用于不可变类型.

这些方法的调用和访问元组元素的方法一致,实际上TupleN继承了ProductN并为从 _1到 _N的方法提供了具体的实现:

scala> val t2 = ("Dean", 29) 
t2: (String, Int) = (Dean,29)
scala> t2._1
res0: String = Dean
scala> t2._2 
res2: Int = 29
scala> t2._3
<console>:36: error: value _3 is not a member of (String, Int)
             t2._3 
                ^

Tuple2中并没有实现 _3这个方法.

The Predef Object

编译器自动导入Predef定义已提供一些有用的定义.

隐式转换

首先Predef提供了很多隐式转换,一组封装了AnyVal类型的转换:

@inline implicit def byteWrapper(x: Byte)
@inline implicit def shortWrapper(x: Short)
@inline implicit def intWrapper(x: Int)
@inline implicit def charWrapper(c: Char)
@inline implicit def longWrapper(x: Long)
@inline implicit def floatWrapper(x: Float) 
@inline implicitdefdoubleWrapper(x:Double)
@inline implicit def booleanWrapper(x: Boolean) = new runtime.RichBoolean(x)

这些 Rich* 类型提供了额外的方法,比如比较方法 <= 和 compare,@inline 注释提醒编译器”内联”这些方法调用.

为什么不把这些方法都放到Byte内而是有两种版本的Byte呢,原因由于字节码的执行需要,这些方法需要在堆中分配一个实例.但是Byte实例,还有其他的AnyVal类型并不分配在堆中,而是像Java中的基本类型一样放在栈中.因此只有在需要这些方法时才会将其放到堆中.

同样对Java的可变数组即scala.col lection.mutable.WrappedArray的实例进行了方法封装,添加了很多集合方法:

implicit def wrapIntArray(xs: Array[Int]): WrappedArray[Int] 
implicit def wrapDoubleArray(xs: Array[Double]): WrappedArray[Double]
implicit def wrapLongArray(xs: Array[Long]): WrappedArray[Long] 
implicit def wrapFloatArray(xs: Array[Float]): WrappedArray[Float] 
implicit def wrapCharArray(xs: Array[Char]): WrappedArray[Char] 
implicit def wrapByteArray(xs: Array[Byte]): WrappedArray[Byte] 
implicit def wrapShortArray(xs: Array[Short]): WrappedArray[Short] 
implicit def wrapBooleanArray(xs: Array[Boolean]): WrappedArray[Boolean] 
implicit def wrapUnitArray(xs: Array[Unit]): WrappedArray[Unit]

为何将每种AnyVal类型的方法都分开了呢? 容器元素的基本类型比数组的执行效率更高,因此为了提高效率,避免使用引用类型的泛型实现.

同样提供了java基本类型的封装与scala中AnyVal之间的转换:

implicit def byte2Byte(x: Byte) = java.lang.Byte.valueOf(x)
implicit def short2Short(x: Short) = java.lang.Short.valueOf(x)
implicit def char2Character(x: Char) = java.lang.Char.valueOf(x)
implicit def int2Integer(x: Int) = java.lang.Int.valueOf(x)
implicit def long2Long(x: Long) = java.lang.Long.valueOf(x)
implicit def float2Float(x: Float) = java.lang.Float.valueOf(x)
implicit def double2Double(x: Double) = java.lang.Double.valueOf(x)
implicit def boolean2Boolean(x: Boolean) = java.lang.Boolean.valueOf(x)

implicit def Byte2byte(x: java.lang.Byte): Byte = x.byteValue 
implicit def Short2short(x: java.lang.Short): Short = x.shortValue
implicitdefCharacter2char(x:java.lang.Character):Char = x.charValue
implicit def Integer2int(x: java.lang.Integer): Int = x.integerValue
implicit def Long2long(x: java.lang.Long): Long = x.longValue
implicit def Float2float(x: java.lang.Float): Float = x.floatValue
implicit def Double2double(x: java.lang.Double): Double = x.doubleValue
implicit def Boolean2boolean(x: java.lang.Boolean): Boolean = x.booleanValue

Type Definitions

Predef中定义了一些类型和类型别名.

我了促进不可变集合的使用,Prefef中定义了一些常用不可集合类型的别名:

type Map[A, +B] = collection.immutable.Map[A, B] 
type Set[A] = collection.immutable.Set[A] type 
Function[-A, +B] = Function1[A, B]

对于量元素和三元素元组的两个方便的别名:

type Pair[+A, +B] = Tuple2[A, B] 
type Triple[+A, +B, +C] = Tuple3[A, B, C]

Condition Checking Methods

有时候需要断言一个条件为真,或者在测试时需要快速失败,Predef为此提供了一些方法:

测试断言为真,否则抛出java.lang.AssertionError.

def assert(assertion: Boolean)

与上面类似,同时携带一个参数并转换为String:

def assert(assertion: Boolean, message: => Any)

和assert类似,但是传达的意思是,当进入一个代码段,如一个方法时,该条件被假定为真的.

def assume(assertion: Boolean, message: => Any)
def require(requirement: Boolean)
def require(requirement: Boolean, message: => Any)

输入输入方法

Miscellaneous Methods

未实现方法:

def ???: Nothing

简单的返回参数x:

def identity[A](x: A): A

当一个隐式参数列表指定简写为[T : M],这时,编译器将隐式的添加隐式参数列表(implicit arg: M[T]):

def implicitly[T](implicit e: T): T

Equality of Objects

The equals Method

我们使用case类来说明比较方法的不同.

case class Person(firstName: String, lastName: String, age: Int)
val p1a = Person("Dean", "Wampler", 29) 
val p1b = Person("Dean", "Wampler", 29) 
val p2 = Person("Buck", "Trends", 30)

equals方法进行的是值比较,二者不需要是同一个实例:

p1a equals p1a      // = true
p1a equals p1b      // = true
p1a equals p2       // = false
p1a equals null     // = false
null equals p1a     // throws java.lang.NullPointerException
null equals null    // throws java.lang.NullPointerException

The == and != Methods

同样是值比较,唯一例外的是左侧为null时:

p1a == null     // = false
null == p1a     // = false
null == null    // = true  (compiler warns that it's always true)

!= 等同于 !(obj1 == obj2).

The eq and ne Methods

eq 用于引用比较,即二者在内存中的位置一致,仅被AnyRef定义:

p1a eq p1a      // = true
p1a eq p1b      // = false
p1a eq p2       // = false
p1a eq null     // = false
null eq p1a     // = false
null eq null    // = true  (compiler warns that it's always true.)

ne 等同于 !(obj1 eq obj2).

Array Equality and the sameElements Method

对两个数组的比较不能得到一个明显的结果:

Array(1, 2) == Array(1, 2) // = false

但是有一个有效的方法,即 sameElements:

Array(1, 2) sameElements Array(1, 2) // = true

但是根据Java的特性,Array是可变的,因此不能有效的进行比较,若果需要,考虑能不能换成序列,比如List:

List(1, 2) == List(1, 2)            // = true
List(1, 2) sameElements List(1, 2)  // = true

Overriding Members of Classes and Traits

类和特质可以声明抽象的字段,方法或类型,这些程序员在创建实例前必须被其派生类完整定义.

覆写成员时Scala强制需要override关键字,但是在子类定义抽象成员时为可选.即子类中该成员仍然未被实现时,添加override关键字是错误的.

使用override关键字的一些好处:

  1. 编译器会检查哪些已被标记为override的成员定义,如果没有任何重写则会抛出错误.
  2. 如果在基类中添加了一个新成员,而该成员name与派生类中成员name重复时会报错.
  3. 添加override关键字会提醒你考虑哪些成员需要重写而那些不需要.

Avoid Overriding Concrete Members: 避免重写实体成员

case class Address(city: String, state: String, zip: String)
case class Employee(name: String, salary: Double, address: Address)

abstract class Payroll {
    def netPay(employee: Employee): Double = {                              // 1
        valfedTaxes =calcFedTaxes(employee.salary)
        val stateTaxes = calcStateTaxes(employee.salary, employee.address) 
        employee.salary - fedTaxes -stateTaxes
    }

    def calcFedTaxes(salary: Double): Double                                // 2
    def calcStateTaxes(salary: Double, address: Address): Double            // 3
}

object Payroll2014 extends Payroll { 
    val stateRate = Map(
        "XX" -> 0.05,
        "YY" -> 0.03,
        "ZZ" -> 0.0)

    def calcFedTaxes(salary: Double): Double = salary * 0.25                // 4
    def calcStateTaxes(salary: Double, address: Address): Double = {
        // Assume the address.state is valid; it's found in the map!
        salary * stateRate(address.state)
    }
}

val tom = Employee("Tom Jones", 100000.0, Address("MyTown", "XX", "12345"))
val jane = Employee("Jane Doe", 110000.0, Address("BigCity", "YY", "67890")) 
Payroll2014.netPay(tom)     // Result: 70000.0
Payroll2014.netPay(jane)    // Result: 79200.0
  1. 方法netPay使用了模板方法模式,它定义了计算薪水的协议
  2. 抽象方法用于计算联邦税
  3. 抽象方法用于计算州税
  4. 实现父类中定义的抽象方法

上面没有使用到任何override关键字.

尽量避免重写父类实体成员,除了toString.不要使用override关键字除非你真的在重写一个实体成员.

Attempting to Override final Declarations

如果一个成员被final关键字生面,则该成员被禁止重写.

Overriding Abstract and Concrete Methods

定义一个抽象类:

abstract class Widget {
    def draw(): Unit
    override def toString() = "(widget)"
}

因为draw并没有实体,它是一个抽象方法,同时Widget也被声明为abstract.任何一个Widget的子类必须实现draw方法或派生自一个实现该方法的父类.

因为所以的AnyRef都已经定义了toString方法,因此这时override关键字是必须的.

下面是一个派生类:

import progscala2.traits.ui2.Clickable

class Button(val label: String) extends Widget with Clickable { 

    // Simple hack for demonstration purposes:
    def draw(): Unit = println(s"Drawing: $this") 

    // From Clickable:
    protected def updateUI(): Unit = println(s"$this clicked; updating UI")

    override def toString() = s"(button: label=$label, ${super.toString()})"
}

Overriding Abstract and Concrete Fields

Overriding fields in traits

trait AbstractT2 {
    println("In AbstractT2:")
    val value: Int
    val inverse = 1.0/value     // 当inverse被初始化时,value的值是多少呢
    println("AbstractT2: value = "+value+", inverse = "+inverse)
}
val obj = new AbstractT2 { 
    println("In obj:")
    val value = 10
}
println("obj.value = "+obj.value+", inverse = "+obj.inverse)

当我们通过一个匿名类继承该特质并尝试运行以上代码时:

In AbstractT2:
AbstractT2: value = 0, inverse = Infinity
In obj:
obj.value = 10, inverse = Infinity

inverse 的值被计算的过早,因为value并没有被实现.

Scala提供了两种方法来解决这个问题.

trait AbstractT2 {
    println("In AbstractT2:")
    val value: Int
    lazy val inverse = 1.0/value    // 添加lazy关键字并注释表println
    // println("AbstractT2: value = "+value+", inverse = "+inverse)
}
val obj = new AbstractT2 { 
    println("In obj:")
    val value = 10
}
println("obj.value = "+obj.value+", inverse = "+obj.inverse)

这时候inverse被正确求值:

In AbstractT2:
In obj:
obj.value = 10, inverse = 0.1

然后,lazy只有在println被注释时有用,如果取消对println的注释,inverse又会被计算为Infinity,因为lazy只有在第一次使用时进行求值.println则对inverse的使用过早.

If a val is lazy, make sure all uses of the val are also as lazy as possible.

另一种解决方式,预初始化字段:

trait AbstractT2 {
    println("In AbstractT2:")
    val value: Int
    val inverse = 1.0/value
    println("AbstractT2: value = "+value+", inverse = "+inverse)
}
val obj=new{  
    // println("In obj:") //  1
    val value = 10
} with AbstractT2

println("obj.value = "+obj.value+", inverse = "+obj.inverse)
  1. 预初始化块 只允许 类型定义 和 实体字段定义.如果使用println则会报错.

这时重新运行则会成功求值:

In AbstractT2:
AbstractT2: value = 10, inverse = 0.1
obj.value = 10, inverse = 0.1

在trait中inverse就已经被求值了.

然后看一个VetoableClicks特质,定义了一个maxAllowed初始化为1:

trait VetoableClicks extends Clickable {    // 1

    // 允许被点击的默认值
    val maxAllowed = 1                      // 2

    private var count = 0

    abstract override def click() = {
        if (count < maxAllowed) {            // 3
            count += 1
            super.click() }
        }    
    }
}
  1. 继承Clickable
  2. 允许的最大点击数
  3. 一旦点击数超过了允许值,不会有更多的点击发送给super

让我们看一下使用中遇到的问题:

trait ObservableClicks extends Clickable with Subject[Clickable] { 
    abstract override def click(): Unit = {                         // 1
        super.click()
        notifyObservers(this) 
    }
}
  1. 注意关键字 abstract override

然后进行测试:

val observableButton =                                                                          // 1
    new Button("Okay") with ObservableClicks with VetoableClicks {
        override val maxAllowed: Int = 2                                                        // 2
    }
assert(observableButton.maxAllowed == 2, s"maxAllowed = ${observableButton.maxAllowed}")        // 3

class ClickCountObserver extends Observer[Clickable] {                                          // 4
    var count = 0
    def receiveUpdate(state: Clickable): Unit = count += 1
}
val clickCountObserver = new ClickCountObserver                                                 // 5
observableButton.addObserver(clickCountObserver)

val n = 5                                                                                       // 6
for (i <- 1 to n) 
    observableButton.click()                                                      
assert(clickCountObserver.count == 2, s"count = ${clickCountObserver.count}. Should be != $n")  // 7
  1. 通过需要的特质构造一个button
  2. 重写特质中的val字段,重写时必须提供完整的定义
  3. 经验证,成功修改了maxAllowed的值
  4. 定义一个管擦者类以记录点击数
  5. 实例化一个观察者并注册到button
  6. 点击button 5 次
  7. 验证管擦者只收到了两次点击,而其他的点击被拒绝了

上面已经看到了重写val字段的定义.如果maxAllowed被声明为var,该如何重写呢:

val observableButton =
    new Button("Okay") with ObservableClicks with VetoableClicks {
        maxAllowed = 2 
    }

重写可变字段时,不再需要override关键字和完整的签名,比如var 和类型.

Overriding fields in classes

在class中重写val和var:

class C1 {
    val name = "C1" 
    var count = 0
}
class ClassWithC1 extends C1 { 
    override val name = "ClassWithC1" 
    count = 1
}
val c = new ClassWithC1() 
println(c.name) 
println(c.count)

重写抽象类型的val和var:

abstract class AbstractC1 { 
    val name: String
    var count: Int
}
class ClassWithAbstractC1 extends AbstractC1 { 
    val name = "ClassWithAbstractC1"
    var count = 1
}
val c = new ClassWithAbstractC1()
println(c.name)
println(c.count)

重写抽象的val时不再需要override关键字.

Overriding Abstract Types

一个BulkReader的例子:

abstract class BulkReader { 
    type In
    val source: In
    def read: String // Read source and return a String 
}

class StringBulkReader(val source: String) extends BulkReader { 
    type In = String
    def read: String = source
} 
...

上面的例子中展示了如何定义一个抽象类型,以及如何在派生类中实现一个抽象类型.

与字段方法不同,不能够重写一个已经定义的类型.

Linearization of an Object’s Hierarchy

由于单继承的原因,如果我们混入多个特质,则多重继承会表现为线性形式.

下面是一个实例:

class C1 {
    def m = print("C1 ")
}
trait T1 extends C1 {
    override def m = { print("T1 "); super.m }
}
trait T2 extends C1 {
    override def m = { print("T2 "); super.m }
}
trait T3 extends C1 {
    override def m = { print("T3 "); super.m }
}
class C2 extends T1 with T2 with T3 {
    override def m = { print("C2 "); super.m }

val c2 = new C2 
c2.m

运行结果:

C2 T3 T2 T1 C1

可以发现,特质中的m方法会根据声明中的顺序从左到右依次调用,然后在最后调用到C1.

然后我们观察构造函数的调用顺序:

class C1 { 
    print("C1 ")
}
trait T1 extends C1 { 
    print("T1 ")
}
trait T2 extends C1 { 
    print("T2 ")
}
trait T3 extends C1 { 
    print("T3 ")
}
class C2 extends T1 with T2 with T3 { 
    println("C2 ")
}
val c2 = new C2

// C1 T1 T2 T3 C2

构造函数的顺序是反向的. 这个调用顺序是有意义的,因为派生类的构造会用到父类中的成员,因此父类必须在派生类构造之前完成自身的构造.

上面第一个例子中在最后省略了两个类型,引用类型的完整线性化其实是以AnyRef和Any结束,因此C2的线性化应该是这样:

C2 T3 T2 T1 C1 AnyRef Any

另外,值类型,所有AnyVal的子类,都被声明为abstract final,编译器对他们的初始化进行管理.因为我们不能从他们派生类,他们的线性化也是简单直接的.

值类(value classe)又是怎样的呢? 如下实例中,值类并不允许我们在类型体内添加打印语句:

class USPhoneNumber(val s: String) extends AnyVal {
    override def toString = {
    val digs = digits(s)
    val areaCode = digs.substring(0,3)
    val exchange = digs.substring(3,6)
    val subnumber = digs.substring(6,10)    // "subscriber number" 
    s"($areaCode) $exchange-$subnumber"
    }
    private def digits(str: String): String = str.replaceAll("""\D""", "") 
}

val number = new USPhoneNumber("987-654-3210") 
// Result: number: USPhoneNumber = (987) 654-3210

当调用m时会打印以下结果:

USPhoneNumber Formatter Digitizer M