此文件頁面特定於 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
實體,而只會在執行階段使用原始 double。
注意:實際上,你可以使用案例類別和/或擴充方法來獲得更簡潔的語法。
何時需要配置
由於 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 val、var 或 val 作為成員。
- …無法由其他類別擴充。
限制範例
本節提供許多在前一節已說明限制的具體範例。
不允許多個建構函數參數
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)
^
值類別無法有延遲 val、var 或 val 作為成員,且無法有巢狀類別、特質或物件
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
}