此文档页面专门针对 Scala 2 中提供的功能,这些功能已在 Scala 3 中删除或被替代功能所取代。除非另有说明,此页面中的所有代码示例均假定你使用的是 Scala 2。
在 Scala 3 中,出于兼容性原因,值类仍然受支持,但实现相同结果的推荐方法是使用 不透明类型。
简介
最初在 SIP-15 中提出并在 Scala 2.10.0 中引入,值类是 Scala 中的一种机制,用于避免分配运行时对象。这是通过定义新的 AnyVal
子类来实现的。
以下显示了一个非常小的值类定义
class Wrapper(val underlying: Int) extends AnyVal
它有一个单一的公有 val
参数,它是底层运行时表示。编译时类型为 Wrapper
,但在运行时,表示为 Int
。值类可以定义 def
,但不能定义 val
、var
或嵌套的 trait
、class
或 object
class Wrapper(val underlying: Int) extends AnyVal {
def foo: Wrapper = new Wrapper(underlying * 19)
}
值类只能扩展通用特性,而不能被自身扩展。通用特性是一个扩展 Any
的特性,只有 def
作为成员,并且不进行初始化。通用特性允许值类进行方法的基本继承,但它们会产生分配开销。例如,
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() // actually requires instantiating a Wrapper instance
本文档的其余部分展示了用例、有关何时发生和不发生分配的详细信息,以及值类限制的具体示例。
扩展方法
值类的一个用例是将它们与隐式类 (SIP-13) 结合起来,用于无分配扩展方法。使用隐式类为定义扩展方法提供了更方便的语法,而值类则消除了运行时开销。一个很好的例子是标准库中的 RichInt
类。 RichInt
使用多个方法扩展了 Int
类型。由于它是一个值类,因此在使用 RichInt
方法时不需要创建 RichInt
的实例。
以下 RichInt
片段展示了它如何扩展 Int
以允许表达式 3.toHexString
implicit class RichInt(val self: Int) extends AnyVal {
def toHexString: String = java.lang.Integer.toHexString(self)
}
在运行时,此表达式 3.toHexString
被优化为对静态对象(RichInt$.MODULE$.toHexString$extension(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
实例,而只会在运行时使用原始双精度浮点数。
注意:在实践中,你可以使用 case 类和/或扩展方法来获得更简洁的语法。
何时需要分配
由于 JVM 不支持值类,Scala 有时需要实际实例化一个值类。有关详细信息,请参阅 SIP-15。
分配摘要
在以下情况下,值类将实际实例化
- 将值类视为另一种类型。
- 将值类分配给数组。
- 执行运行时类型测试,例如模式匹配。
分配详细信息
每当将值类视为另一种类型(包括通用特质)时,都必须实例化实际值类的实例。例如,考虑 Meter
值类
trait Distance extends Any
case class Meter(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(i: Int) extends AnyVal
val p = P(3)
p match { // new P instantiated here
case P(3) => println("Matched 3")
case P(_) => println("Not 3")
}
限制
值类目前有几个限制,部分原因是 JVM 本身不支持值类的概念。有关值类实现及其限制的详细信息,请参阅 SIP-15。
限制摘要
值类 …
- … 必须只有一个主构造函数,该构造函数恰好有一个公共的 val 参数,其类型不是用户定义的值类。(从 Scala 2.11.0 开始,该参数可以是非公共的。)
- … 可能没有
@specialized
类型参数。 - … 可能没有嵌套或局部类、特质或对象。
- … 不能定义具体的
equals
或hashCode
方法。 - … 必须是顶级类或静态可访问对象的一部分。
- … 只能将 def 作为成员。特别是,它不能将 lazy vals、vars 或 vals 作为成员。
- … 无法被其他类扩展。
限制示例
本节提供了许多在上节中已经描述的限制的具体示例。
不允许有多个构造函数参数
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
,因此它不能是按名称参数
NoByName.scala:1: error: `val` parameters may not be call-by-name
class NoByName(val x: => Int) extends AnyVal
^
Scala 不允许延迟 val 构造函数参数,因此也不允许。不允许有多个构造函数
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)
^
值类不能有延迟 vals、vars 或 vals 作为成员,也不能有嵌套类、特质或对象
class NoLazyMember(val evaluate: () => Double) extends AnyVal {
val member: Int = 3
var y: Int = 4
lazy val x: Double = evaluate()
object NestedObject
class NestedClass
}
Invalid.scala:2: error: this statement is not allowed in value class: val member: Int = 3
val member: Int = 3
^
Invalid.scala:3: error: this statement is not allowed in value class: var y: Int = 4
var y: Int = 4
^
Invalid.scala:4: error: this statement is not allowed in value class: lazy val x: Double = NoLazyMember.this.evaluate.apply()
lazy val x: Double = evaluate()
^
Invalid.scala:5: error: value class may not have nested module definitions
object NestedObject
^
Invalid.scala:6: error: value class may not have nested class definitions
class NestedClass
^
请注意,也不允许本地类、特质和对象,如下所示
class NoLocalTemplates(val x: Int) extends AnyVal {
def aMethod = {
class Local
...
}
}
Local.scala:3: error: implementation restriction: nested class is not allowed in value class
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
^
此外,结构类型不能在方法参数或返回类型中使用值类
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 =
^
值类不能扩展非通用特质,值类本身也不能被扩展
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
修饰符,但它被假定为已存在。
另一个限制是仅支持一个类参数,即值类必须是顶级类或静态可访问对象的一个成员。这是因为嵌套值类将需要引用封闭类的第二个参数。因此,这是不允许的
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
}