trait(特质) 是可带具体实现的接口,一个类只能单继承但可混入多个特质;包与导入决定代码的组织与可见性。本节合并讲完对象系统的剩余骨架。
日志能力、重试能力、指标上报能力,如果都用继承实现,一个"可重试且可上报且可记录的服务"需要 2 的 3 次方个类组合——继承爆炸。特质就是解法:
trait Logger: def log(msg: String): Unit = println(s"[LOG] $msg") trait Retriable: def maxRetry: Int = 3 def runWithRetry[A](body: => A): A = ??? class SyncService extends Logger, Retriable: def sync(): Unit = log("start sync")
类只能 extends 一个父类,但可以混入任意多个特质(Scala 3 用逗号分隔,替代旧版的 with)。

混入顺序有讲究:特质按声明顺序线性化,后面的特质先初始化、其 super 调用指向更早的特质。设计"过滤链"时这个顺序就是过滤的次序。
trait Base: def render: String trait A extends Base: override def render = s"A(" + super.render + ")" trait B extends Base: override def render = s"B(" + super.render + ")" class Chain extends A, B // 线性化:B 在 A 外层 (new Chain).render // "B(A(Base))"
abstract class Shape: def area: Double // 抽象方法 sealed trait Event // 密封:子类型只能在本文件定义 case class Click(x: Int, y: Int) extends Event case class KeyPress(code: Int) extends Event case object Idle extends Event
sealed 告诉编译器"子类型清单是封闭的",于是模式匹配可以穷举检查——漏一个分支直接编译警告(Scala 3 甚至报错)。这是"让非法状态不可表示"的第一件武器,第五章还会升级。
package com.shop.order // 文件顶部声明 import com.shop.pay.* // 通配导入 import com.shop.pay.Currency as Cur // 改名,避免冲突 import java.time.{LocalDate, LocalDateTime} // 选择性导入
Scala 的 import 可以出现在任意作用域里,甚至函数体内——只在那段代码可见,缩小名字污染。默认每个文件都已导入 scala.*、java.lang.* 与所在包。
可见性默认全公开;private 限本类/伴生对象,protected 限子类,包私有写作 private[order](order 为包名)——比 Java 的默认包可见更精细。
| 维度 | trait | abstract class |
|---|---|---|
| 多重继承 | 可混入多个 | 只能单继承 |
| 构造参数 | 不能带 | 可以 |
| 适用 | 能力组合 | 有字段的模板基类 |
我的默认:先用 trait,需要构造参数或强模板时才用抽象类。

一个类混入多个特质、且多个特质都改写同一方法时,调用顺序由 linearization(线性化)决定。用一段完整代码把过程演出来:
trait Base: def msg: String = "base" trait Upper extends Base: override def msg = s"[${super.msg}.upper]" trait Bang extends Base: override def msg = s"[${super.msg}bang]" class Widget extends Base, Upper, Bang // 混入顺序从左到右 Widget().msg // 结果:[[base.upper]bang]
推演:Bang 最后混入所以最外层,它的 super 指向 Upper,Upper 的 super 指向 Base。于是输出从内到外层层包裹。与 C++ 的虚继承不同,Scala 给出确定性的顺序——线性化把继承图压成一条链,super 沿链上溯。当两段代码库的特质意外碰撞时,读输出就能反推出混入顺序是否写反。
trait Logged: def log(msg: String): Unit = println(s"[log] $msg") trait Audited extends Logged: def audit(action: String): Unit = log(s"audit: $action") class OrderService extends Audited: def place(n: Int): Unit = audit(s"place order $n") OrderService().place(3) // [log] audit: place order 3
这里的能力不是字段而是行为切片:审计依赖日志,服务混入审计即同时获得两者。相比 Java 接口的默认方法,特质可以带状态、可以互相叠加,是组合复用的主战场。
private[包名] 提供细粒度可见性。