第9章 泛型与隐式
类型参数与上下界、隐式参数与隐式类:这两块是读懂 Spark / Flink 源码的门槛,也是 Scala 最“难”的部分。
本章学习目标
- 会给类和函数加类型参数,并写上下界
- 能看懂 Ordering 这类隐式参数的作用
- 知道 Scala 3 用 given / using 取代了 implicit
- 能把一段重复代码用泛型收敛
本章导览
核心内容
9.1
类型参数与上下界:泛型类与泛型方法
用类型参数写出可复用的泛型类和方法,并用上下界约束范围。
能定义泛型类和泛型方法,并让编译器推断类型实参能解释 <: 上界与 >: 下界分别限制什么能借助 Ordering 上下文界定写出可比较的泛型方法
知识点
类型参数写在方括号里
- 类 Box[A] 的 A 在创建实例时确定,方法 def head[A](xs: List[A]): A 在调用时推断。
- 泛型让容器和算法不绑定具体类型,同时保留编译期类型检查。
- 调用泛型方法通常不必写 List[Int],编译器会从实参推断出 A。
上界 <: 与下界 >:
- A <: Animal 表示 A 必须是 Animal 或它的子类,因此可以安全调用 Animal 的方法。
- A >: Dog 表示 A 必须是 Dog 或它的父类型,适合把 Dog 放进更宽的集合。
- 边界写错时常见报错是 type arguments do not conform to bounds。
上下文界定借来 Ordering
- [A: Ordering] 等价于多一个隐式参数 (implicit ord: Ordering[A])。
- List[Int].sorted 能工作,是因为作用域里有 Ordering.Int;自定义类型要自己提供。
- JVM 会做类型擦除,运行时无法区分 List[Int] 和 List[String],别用 isInstanceOf 判断元素类型。
协变与逆变先认符号
- Box[+A] 是协变:Box[Dog] 可以当 Box[Animal] 用,适合不可变容器。
- Box[-A] 是逆变:方向反过来,函数参数位置常出现这种需求。
- 初学先把默认的不变写对,确实需要放宽替换关系时再加 + 或 -。
代码示例
▶ 泛型类 Box 与泛型方法
case class Box[A](value: A) {
def map[B](f: A => B): Box[B] = Box(f(value))
def pair[B](other: B): Box[(A, B)] = Box((value, other))
}
object GenericBox {
def first[A](xs: List[A]): Option[A] = xs.headOption
def middle[A](xs: List[A]): Option[A] =
if (xs.size < 3) None else Some(xs(xs.size / 2))
def main(args: Array[String]): Unit = {
val b = Box(21).map(_ * 2)
println(b)
println(b.pair("answer"))
println(first(List("a", "b")))
println(middle(List(1, 2, 3, 4, 5)))
println(first(List.empty[Double]))
}
}👀 运行结果
Box(42) Box((42,answer)) Some(a) Some(3) None
▶ 上下界约束泛型范围
abstract class Animal(val name: String) {
def sound: String
}
class Dog(name: String) extends Animal(name) {
def sound = "Woof"
}
class Cat(name: String) extends Animal(name) {
def sound = "Meow"
}
object BoundsDemo {
def introduce[A <: Animal](a: A): String = s"${a.name} 说 ${a.sound}"
def addDog[A >: Dog](xs: List[A], dog: Dog): List[A] = dog :: xs
def main(args: Array[String]): Unit = {
println(introduce(new Dog("旺财")))
println(introduce(new Cat("咪咪")))
val animals: List[Animal] = List(new Cat("咪咪"))
val more = addDog(animals, new Dog("旺财"))
println(more.map(_.name))
println(addDog(List(new Dog("A")), new Dog("B")).map(_.name))
}
}👀 运行结果
旺财 说 Woof 咪咪 说 Meow List(旺财, 咪咪) List(B, A)
常见错误
⚠️ 踩坑与解决
给 A <: Animal 传了 String
→ 上界已经把可选类型缩小到 Animal 族,String 不满足约束,编译期就拒绝。;要么传 Animal 的子类,要么去掉上界、换成不依赖 Animal 能力的类型参数。在 A >: Dog 的方法体里调用 a.sound
→ 下界只保证 A 比 Dog 宽,A 可能是 Animal 甚至 Any,编译器不知道它有 sound。;要使用 Dog 的能力应写 A <: Dog;下界是用来把 Dog 装进更宽的集合。用 isInstanceOf[List[Int]] 判断元素类型
→ JVM 类型擦除后 List[Int] 和 List[String] 运行时都是 List,判断永远为真。;需要区分时携带 ClassTag,或在数据模型里显式放一个类型标签。
💡 先写具体类型的版本,再把重复部分换成 A;上界决定“能调用什么”,下界决定“能装进什么”。
9.2
implicit:隐式参数、隐式类与 Scala 3 的 given
理解隐式参数与隐式类,知道 Scala 3 的 given/using 写法。
能写隐式参数,让编译器自动注入 Ordering 或自定义能力能用 implicit class 给现有类型增加方法能说出 implicit、given/using 与上下文界定的对应关系
知识点
隐式参数是编译器补上的参数
- def top[A](xs: List[A])(implicit ord: Ordering[A]): A 调用时只传 xs。
- 编译器在当前作用域找类型匹配的 implicit 值,找到就自动填入最后一个参数列表。
- 找不到时会报 could not find implicit value,通常说明缺实例或忘 import。
上下文界定是语法糖
- [A: Ordering] 会展开成隐式参数列表,方法体里用 implicitly[Ordering[A]] 取出实例。
- Ordering、ClassTag 这类 type class 都爱用上下文界定,让签名保持干净。
- 自定义 type class 也可以用 implicit val 或 implicit object 提供实例。
隐式类给现有类型加方法
- implicit class 必须定义在 object、class 或 trait 里,不能裸放在包顶层。
- 导入后,"abc".shout 会被编译器改写成 RichString("abc").shout。
- 隐式类构造器只能有一个非隐式参数,而且不能是 case class。
Scala 3 的写法差异
- Scala 3 用 given/using 取代隐式定义与隐式参数,并支持省略花括号的缩进语法。
- 本课统一按 Scala 2.13 的 implicit 与花括号写法提交,不要把两种风格混在一起。
代码示例
▶ 隐式参数与自定义 Ordering
case class User(name: String, age: Int)
object ImplicitParamDemo {
implicit val userOrdering: Ordering[User] =
Ordering.by(u => (u.age, u.name))
def top[A](xs: List[A])(implicit ord: Ordering[A]): A = xs.max
def sorted[A: Ordering](xs: List[A]): List[A] = xs.sorted
def main(args: Array[String]): Unit = {
val users = List(User("张三", 20), User("李四", 22), User("王五", 22))
println(top(users))
println(sorted(users).map(_.name))
println(top(List(3, 9, 2)))
println(sorted(List("pear", "apple", "fig")))
}
}👀 运行结果
User(王五,22) List(张三, 李四, 王五) 9 List(apple, fig, pear)
▶ 隐式类扩展现有类型
object RichDemo {
implicit class RichString(private val s: String) {
def shout: String = s.toUpperCase + "!"
def words: List[String] = s.split("\\s+").toList.filter(_.nonEmpty)
}
implicit class RichInt(private val n: Int) {
def repeated[A](f: Int => A): List[A] = (1 to n).map(f).toList
}
def main(args: Array[String]): Unit = {
println("hello scala".shout)
println("a b c".words)
println(3.repeated(i => i * i))
}
}👀 运行结果
HELLO SCALA! List(a, b, c) List(1, 4, 9)
常见错误
⚠️ 踩坑与解决
调用 top(users) 报 could not find implicit value
→ 作用域里没有 Ordering[User],或隐式实例放在别的 object 却没有 import。;把实例放进伴生对象让编译器自动查找,或在调用处显式 import。把 implicit class 放在包顶层
→ Scala 2 顶层不能直接放 implicit class,它必须是 object、class 或 trait 的成员。;把隐式类包进 object,再 import 该对象的成员。
💡 隐式不是魔法,而是让编译器替你传参;报错时先把类型写全,缺哪个实例通常就一目了然。