值类和通用特质

语言
此文档页面专门针对 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,但不能定义 valvar 或嵌套的 traitclassobject

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

分配摘要

在以下情况下,值类将实际实例化

  1. 将值类视为另一种类型。
  2. 将值类分配给数组。
  3. 执行运行时类型测试,例如模式匹配。

分配详细信息

每当将值类视为另一种类型(包括通用特质)时,都必须实例化实际值类的实例。例如,考虑 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

限制摘要

值类 …

  1. … 必须只有一个主构造函数,该构造函数恰好有一个公共的 val 参数,其类型不是用户定义的值类。(从 Scala 2.11.0 开始,该参数可以是非公共的。)
  2. … 可能没有 @specialized 类型参数。
  3. … 可能没有嵌套或局部类、特质或对象。
  4. … 不能定义具体的 equalshashCode 方法。
  5. … 必须是顶级类或静态可访问对象的一部分。
  6. … 只能将 def 作为成员。特别是,它不能将 lazy vals、vars 或 vals 作为成员。
  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,因此它不能是按名称参数

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
}

此页面的贡献者