Value Classes and Universal Traits
http://docs.scala-lang.org/overviews/core/value-classes.html
简介
在Scala中,值类是避免分配运行时对象(avoid allocating runtime objects)的新机制。这是通过定义一个AnyVal的子类来完成的。该特性在SIP-15中被提出。下面展示了一个简单的值类定义:class Wrapper(val underlying: Int) extends AnyVal
这个类有一个单一的、公共的val参数,它是底层的运行时表示(underlying runtime representation)。编译的时候,它是Wrapper类型的,而运行时,它表示一个Int。一个值类可以定义方法(defs),但不能定义属性(vals、vars),也不能嵌套特质(traits)、类或对象:
class Wrapper(val underlying: Int) extends AnyVal {
def foo: Wrapper = new Wrapper(underlying * 19)
}
一个值类只能扩展普通特质,但自身不能被扩展(值类是final型的)。普通特质是指那些扩展自Any(trait XXX extends Any)、只有方法和成员变量、没有初始化的特质。对于值类而言,普通特质允许基本的方法继承,但他们会产生资源分配开销。例如:
trait Printable extends Any {
def print(): Unit = println(this)
}
class Wrapper(val underlying: Int) extends AnyVal with Printable
val w = new Wrapper(3)
w.print() // 实际上需要实例化一个Wrapper对象
这个文档的剩余部分展示了一些用法示例,详细说明何时会进行资源分配,而何时不会发生,并且用具体的例子说明值类的局限。
扩展方法
一种值类的用法,是将它们和隐式类(SIP-13)结合使用(for allocation-free extension methods 啥意思?)。使用一个隐式类提供一个更方便的语法来定义扩展方法,而且值类移除了运行时的开销。一个很好的例子就是标准类库中的RichInt。 RichInt用几个方法扩展了Int类型。因为是值类,所以当使用RichInt的方法时不需要创建它的实例。以下RichInt的代码片段,展示了它扩展Int从而允许表达式3.toHexString。
class RichInt(val self: Int) extends AnyVal {
def toHexString: String = java.lang.Integer.toHexString(self)
}
在运行时,表达式3.toHexString会优化成相当于对静态对象的方法调用(RichInt$.MODULE$.extension$toHexString(3)),而不是对新实例化的对象的方法调用。
正确性
值类的另一个用法是得到一个类型安全的数据类型,而且没有运行时资源分配的开销。例如代表一个距离的数据类型代码片段看起来是这样的:class Meter(val value: Double) extends AnyVal {
def +(m: Meter): Meter = new Meter(value + m.value)
}
两个距离增加的代码就像这样:
val x = new Meter(3.4)
val y = new Meter(4.3)
val z = x + y
运行时,并没有真的创建两个Meter的实例,而是用原生的double类型。 注意:在实践中,你可以使用case class和/或扩展方法来获得更简洁的语法。
必要的资源分配
由于JVM不支持值类,Scala有时候需要实例化值类。具体细节请查看SIP-15。
分配摘要
在这些情况下,值类会被实例化:1、把值类当做另一个类型。
2、用值类指定数组。
3、运行时类型检查,比如模式匹配(pattern matching)。
分配细节
每当值类被当做另一个类型时(包括普通特质),值类必须被实例化。看一个例子,考虑一下Meter值类:trait Distance extends Any
case class Meter(val value: Double) extends AnyVal with Distance
如果一个方法接受Distance类型的值,那么它需要一个真正的Meter实例。在接下来的例子中,Meter类会被实例化。
def add(a: Distance, b: Distance): Distance = ...
add(Meter(3.4), Meter(4.3))
如果add方法签名用下面的代替:
def add(a: Meter, b: Meter): Meter = ...
那么资源分配就不是必需的。这个规则的另一个实例是,当值类被用作类型参数。例如,调用identity时,必需创建Meter的实例:
def identity[T](t: T): T = t
identity(Meter(5.0))
另一个资源分配的情况是指定一个数组,即使它是这个值类的数组。例如:
val m = Meter(5.0)
val array = Array[Meter](m)
这个数组包含实际的Meter实例,而非原生类型double。
最后,类型检查(比如模式匹配或asInstanceOf)必需实例化值类:
case class P(val i: Int) extends AnyVal
val p = new P(3)
p match { // new P instantiated here
case P(3) => println("Matched 3")
case P(x) => println("Not 3")
}
局限性
目前值类有几个局限,很大程度上是因为JVM没有原生支持值类的概念。值类的实现详情和它们的局限性,请看SIP-15。
局限性摘要
一个值类...1、只有一个主构造器,只有一个公共的val参数,并且类型不能是值类。
2、不能有专门的类型参数。
3、不能有嵌套或本地类,特质或对象。
4、不能定义equals或hashCode方法。
5、必需是顶级类或一个顶级可访问对象的成员。
6、只能有成员方法。尤其不能有lazy val,var或val作为成员。
7、不能扩展另一个类。
局限性的例子
这节提供了很多关于这些缺陷的具体影响,它们在“必要的资源分配”节没有描述。
不允许多个构造器参数:class Complex(val real: Double, val imag: Double) extends AnyVal
Scala编译器会生成如下的错误消息:
Complex.scala:1: error: value class needs to have exactly one public val parameter
class Complex(val real: Double, val imag: Double) extends AnyVal
^
由于构造器参数必须是val,所以不能是传名参数(by-name parameter,Scala编程一书中翻译为传名参数):
NoByName.scala:1: error: `val' parameters may not be call-by-name
class NoByName(val x: => Int) extends AnyVal
^
Scala不允许延迟初始化的构造器参数,所以值类也不支持。也不允许多构造器。
(按我的理解,一个无参函数字面量()=>Int,去掉()就是传名函数,于是就可以用“1+2”作为入参,来代替“()=>{1+2}”这种恶心的写法)
class Secondary(val x: Int) extends AnyVal {
def this(y: Double) = this(y.toInt)
}
Secondary.scala:2: error: value class may not have secondary constructors
def this(y: Double) = this(y.toInt)
^
值类不能有lazy val或val的成员,也不能有嵌套类,特质,或对象。
class NoLazyMember(val evaluate: () => Double) extends AnyVal {
val member: Int = 3
lazy val x: Double = evaluate()
object NestedObject
class NestedClass
}
Invalid.scala:2: error: this statement is not allowed in value class: private[this] val member: Int = 3
val member: Int = 3
^
Invalid.scala:3: error: this statement is not allowed in value class: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply()
lazy val x: Double = evaluate()
^
Invalid.scala:4: error: value class may not have nested module definitions
object NestedObject
^
Invalid.scala:5: error: value class may not have nested class definitions
class NestedClass
^
注意本地类,特质和对象也是不允许的:
class NoLocalTemplates(val x: Int) extends AnyVal {
def aMethod = {
class Local
...
}
}
当前的实现约束是,值类不能被嵌套:
class Outer(val inner: Inner) extends AnyVal
class Inner(val value: Int) extends AnyVal
Nested.scala:1: error: value class may not wrap another user-defined value class
class Outer(val inner: Inner) extends AnyVal
^
此外,不能在结构类型(structural types)的方法参数或返回类型中使用值类:
class Value(val x: Int) extends AnyVal
object Usage {
def anyValue(v: { def value: Value }): Value =
v.value
}
Struct.scala:3: error: Result type in structural refinement may not refer to a user-defined value class
def anyValue(v: { def value: Value }): Value =
^
值类不能扩展非普通特质(non-universal trait),值类也不能被扩展:
trait NotUniversal
class Value(val x: Int) extends AnyVal with notUniversal
class Extend(x: Int) extends Value(x)
Extend.scala:2: error: illegal inheritance; superclass AnyVal
is not a subclass of the superclass Object
of the mixin trait NotUniversal
class Value(val x: Int) extends AnyVal with NotUniversal
^
Extend.scala:3: error: illegal inheritance from final class Value
class Extend(x: Int) extends Value(x)
^
第二个错误信息表明,尽管值类没有显示声明final修饰符,但它是假定存在的。
另一个限制是...(中间这一大段是啥意思,各种什么什么从句要命啊)值类必须是顶级类或可访问的静态对象的成员。
(Another limitation that is a result of supporting only one parameter to a class is that a value class must be top-level or a member of a statically accessible object.)
class Outer {
class Inner(val x: Int) extends AnyVal
}
Outer.scala:2: error: value class may not be a member of another class
class Inner(val x: Int) extends AnyVal
^
但是下面的例子是允许的,因为它被顶级对象包围:
object Outer {
class Inner(val x: Int) extends AnyVal
}
(好了,就到这吧)
2013-1-8
到家8点多了,写了一会儿年会节目的段子,然后想找几个辅助翻译的工具,都不太给力,还是用网页翻译和词典吧。快11点了,该睡觉了,才翻译完《简介》部分,要是按这个速度,估计我看完一本英文原版书的话得需要一年,呵呵。
2013-1-9
上六天班了,还有两天,坚持坚持。今天整了一小段《扩展方法》,翻译这活真累,以后就一小段一小段翻译吧。
2013-1-24
中间隔了好长时间,中间有一周晚上到家就玩游戏,然后又去看sbt。今天趁公司没事,偷摸把剩下的弄完。剩下的那些好长啊,还有好多看不懂的,哎。