不透明类型
零成本抽象
不透明类型 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。
什么是不透明类型?
不透明类型是 Scala 3 中用于创建零成本抽象的功能。它们在编译时提供独立类型,但编译后会直接使用底层类型,不会产生运行时包装。
- 具有包装类的类型安全性。
- 没有额外的分配开销。
object Distances:
opaque type Meters = Double
object Main:
def main(args: Array[String]): Unit =
println("Opaque types compile to their underlying type")声明不透明类型
在对象或类中声明不透明类型。在该作用域之外,Meters 和 Double 会被视为不同类型。
object Distances:
opaque type Meters = Double
def meters(d: Double): Meters = d
def toDouble(m: Meters): Double = m
object Main:
def main(args: Array[String]): Unit =
val d = Distances.meters(5.0)
println(Distances.toDouble(d))不透明性的边界
在定义作用域内,不透明类型与其底层类型可以互换。在外部只能看到不透明类型,因此无法在需要 Meters 的地方误传原始 Double。
object Distances:
opaque type Meters = Double
def apply(d: Double): Meters = d
def show(m: Meters): String = s"$m m"
object Main:
def main(args: Array[String]): Unit =
val m = Distances(10.0)
println(Distances.show(m))添加扩展方法
在同一作用域中定义扩展方法,即可为不透明类型添加行为。调用者可以使用便捷操作,同时底层类型不会被公开。
object Distances:
opaque type Meters = Double
def apply(d: Double): Meters = d
extension (m: Meters)
def +(other: Meters): Meters = m + other
def value: Double = m
object Main:
def main(args: Array[String]): Unit =
val total = Distances(3.0) + Distances(4.0)
println(total.value)避免类型混淆
最大的优势是:基于同一底层类型定义的两个不透明类型互不兼容。Meters 不会与 Kilometers 混淆,即使它们都是 Double。
object Units:
opaque type Meters = Double
opaque type Kilometers = Double
def m(d: Double): Meters = d
def km(d: Double): Kilometers = d
def toMeters(k: Kilometers): Meters = k * 1000
object Main:
def main(args: Array[String]): Unit =
val far = Units.km(2.0)
println(Units.toMeters(far))带边界的不透明类型
您可以使用 <: 为不透明类型指定上界。这样既能公开部分 API,又能隐藏确切表示方式。
object Ids:
opaque type UserId <: Int = Int
def apply(i: Int): UserId = i
object Main:
def main(args: Array[String]): Unit =
val id = Ids(99)
println(id + 1)带验证的智能构造
将不透明类型与验证工厂结合起来,确保值始终有效。构造器保持私有,只有经过验证的工厂是公开的。
object Ages:
opaque type Age = Int
def of(i: Int): Option[Age] =
if i >= 0 && i < 150 then Some(i) else None
extension (a: Age) def value: Int = a
object Main:
def main(args: Array[String]): Unit =
println(Ages.of(30).map(_.value))
println(Ages.of(-1))零运行时开销
与值类或样例类包装器不同,不透明类型在运行时不会创建对象。在 JVM 中,Meters 的 List 实际上就是 double 的列表,因此不会发生装箱。
object Temps:
opaque type Celsius = Double
def c(d: Double): Celsius = d
extension (t: Celsius) def value: Double = t
object Main:
def main(args: Array[String]): Unit =
val readings = List(Temps.c(20.0), Temps.c(21.5))
println(readings.map(_.value).sum)不透明类型与类型别名
普通的 type X = Y 别名是透明的:X 和 Y 在任何地方都完全可以互换。不透明类型则会在其作用域之外隐藏这种等同性,从而提供真正的类型安全。
object Demo:
type Name = String // transparent alias
opaque type Email = String // opaque
def email(s: String): Email = s
extension (e: Email) def raw: String = e
object Main:
def main(args: Array[String]): Unit =
val e = Demo.email("a@b.com")
println(e.raw)不透明类型与样例类包装器
像 case class Meters(value: Double) 这样的样例类包装器同样能提供类型安全,但会分配对象。不透明类型可以提供相同的安全性,同时拥有原始类型的性能。
object Money:
opaque type Cents = Long
def cents(n: Long): Cents = n
extension (c: Cents)
def +(o: Cents): Cents = c + o
def value: Long = c
object Main:
def main(args: Array[String]): Unit =
val total = Money.cents(150) + Money.cents(50)
println(total.value)何时使用不透明类型
如果您希望在性能关键路径或大型集合中使用强领域类型,请使用不透明类型。
- 区分不同的数量(Meters 与 Seconds)。
- 强制执行经过验证的不变量。
- 避免包装器带来的分配开销。
object Geo:
opaque type Latitude = Double
def lat(d: Double): Option[Latitude] =
if d >= -90 && d <= 90 then Some(d) else None
extension (l: Latitude) def value: Double = l
object Main:
def main(args: Array[String]): Unit =
println(Geo.lat(41.0).map(_.value))快速检查
测试您对不透明类型的理解。
回顾
您学习了 Scala 3 的不透明类型。
opaque type X = Y会在其作用域之外隐藏等同性。- 为 API 提供工厂和
extension方法。 - 基于同一底层类型的不同不透明类型彼此不兼容。
- 通过
<:指定可选的上界,以公开部分 API。 - 运行时开销为零,不同于样例类包装器。
object Ids:
opaque type OrderId = String
def apply(s: String): OrderId = s
extension (o: OrderId) def raw: String = o
object Main:
def main(args: Array[String]): Unit =
val id = Ids("ORD-1")
println(id.raw)常见问题解答
「不透明类型」课时是免费的吗?
是的 — 「不透明类型」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。
「不透明类型」这节课中我会学到什么?
零成本抽象 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「不透明类型」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?
能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。