Scala 泛型:类型安全与灵活性的平衡
泛型是 Scala 中实现类型抽象的核心机制,它允许在定义类、特质、方法时使用类型参数,从而编写与具体类型无关的通用代码。Scala 泛型不仅支持类似 Java 的上下界约束,还引入了协变、逆变等独特特性,进一步增强了类型系统的灵活性。本文将全面解析 Scala 泛型的用法与高级特性。
泛型基础:类型参数的定义与使用
Scala 泛型使用方括号 [] 定义类型参数,可用于类、特质、方法等,实现代码的复用与类型安全。
泛型类
在类定义中声明类型参数,使类能处理多种类型的数据:
1 2 3 4 5 6 7 8 9 10 11 12
| class Box[T](val content: T) { def getContent: T = content }
val intBox = new Box[Int](10) val strBox = new Box[String]("hello")
println(intBox.getContent) println(strBox.getContent)
|
泛型方法
在方法中声明类型参数,使方法能独立于类的类型参数处理不同类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| object GenericUtils { def swap[T](array: Array[T], i: Int, j: Int): Unit = { val temp = array(i) array(i) = array(j) array(j) = temp } }
val intArray = Array(1, 2, 3) GenericUtils.swap(intArray, 0, 2) println(intArray.mkString(","))
val strArray = Array("a", "b", "c") GenericUtils.swap(strArray, 1, 2) println(strArray.mkString(","))
|
类型边界:限制泛型的范围
Scala 允许通过上界和下界限制类型参数的范围,确保泛型代码只接受特定类型或其子类型 / 父类型。
上界(<:)
上界限制类型参数必须是指定类型的子类型(类似 Java 的 extends)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Animal(val name: String) { def eat: Unit = println(s"$name 正在吃东西") }
class Dog(name: String) extends Animal(name) // 子类 Cat class Cat(name: String) extends Animal(name)
// 泛型类 PetShop,类型参数 T 必须是 Animal 的子类型(上界) class PetShop[T <: Animal](val pets: List[T]) { def feedAll(): Unit = pets.foreach(_.eat) }
val dogShop = new PetShop[Dog](List(new Dog("Buddy"), new Dog("Max"))) dogShop.feedAll()
|
说明:T <: Animal 确保 T 是 Animal 的子类型,因此 PetShop 中可安全调用 Animal 的方法(如 eat)。
下界(>:)
下界限制类型参数必须是指定类型的父类型(类似 Java 的 super)。
1 2 3 4 5 6 7 8 9 10 11 12
| def addElement[T](list: List[T], elem: T): List[T] = elem :: list
def addAnimal[T >: Dog](list: List[T], dog: Dog): List[T] = dog :: list
val animalList: List[Animal] = List(new Cat("Mimi")) val newList = addAnimal(animalList, new Dog("Buddy")) newList.foreach(animal => println(animal.name))
|
说明:T >: Dog 确保 T 是 Dog 的父类型(如 Animal),因此 Dog 实例可安全添加到 List[T] 中。
协变、逆变与不变:泛型类型的继承关系
Scala 泛型的独特之处在于支持协变(Covariance) 和逆变(Contravariance),用于描述泛型类型之间的继承关系。这一特性解决了 Java 中 List<String> 不是 List<Object> 子类型的问题。
基本概念
假设有类型 C[T] 和两个类 A、B,其中 A 是 B 的子类型(A <: B):
- 协变(
C[+T]):C[A] <: C[B](C[A] 是 C[B] 的子类型,与 A 和 B 的关系一致)。
- 逆变(
C[-T]):C[B] <: C[A](C[B] 是 C[A] 的子类型,与 A 和 B 的关系相反)。
- 不变(
C[T]):C[A] 和 C[B] 无继承关系(默认行为)。
协变(+T)
协变适用于生产者类型(只返回 T,不消费 T),如只读集合。
1 2 3 4 5 6 7 8 9 10 11
| val dogs: List[Dog] = List(new Dog("Buddy")) val animals: List[Animal] = dogs
class ReadOnlyBox[+T](val content: T) { def get: T = content }
val dogBox: ReadOnlyBox[Dog] = new ReadOnlyBox(new Dog("Max")) val animalBox: ReadOnlyBox[Animal] = dogBox
|
逆变(-T)
逆变适用于消费者类型(只接收 T,不返回 T),如函数或处理器。
1 2 3 4 5 6 7 8 9 10
| class AnimalHandler[-T] { def handle(animal: T): Unit = println(s"处理动物:${animal.name}") }
val animalHandler: AnimalHandler[Animal] = new AnimalHandler[Animal]() val dogHandler: AnimalHandler[Dog] = animalHandler
dogHandler.handle(new Dog("Buddy"))
|
为什么逆变合理?
AnimalHandler[Animal] 能处理所有 Animal(包括 Dog),因此它可以安全地作为 AnimalHandler[Dog] 使用(专门处理 Dog)。
不变(默认)
如果泛型类既生产又消费 T,则必须是不变的,否则会导致类型不安全。
1 2 3 4 5 6 7 8 9
| class MutableBox[T](var content: T) { def get: T = content def set(value: T): Unit = content = value }
val dogBox: MutableBox[Dog] = new MutableBox(new Dog("Buddy"))
|
为什么禁止协变?
若允许 MutableBox[Dog] 赋值给 MutableBox[Animal],则可能通过 animalBox.set(new Cat("Mimi")) 向 dogBox 中放入 Cat,导致类型混乱。
泛型通配符(_)
与 Java 类似,Scala 可用通配符 _ 表示 “任意类型”,结合上下界使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def printAnimals(list: List[_ <: Animal]): Unit = { list.foreach(animal => println(animal.name)) }
def addDogToList(list: List[_ >: Dog]): List[_ >: Dog] = { new Dog("Buddy") :: list }
val dogs = List(new Dog("Max")) val cats = List(new Cat("Mimi")) printAnimals(dogs) printAnimals(cats)
val animals = List(new Animal("Generic Animal")) val newList = addDogToList(animals)
|
泛型的应用场景
- 集合框架:Scala 集合(如
List、Set、Map)广泛使用泛型,List[+T] 是协变的,Map[K, +V] 中值类型是协变的。
- 函数类型:Scala 函数类型
Function1[-T, +R] 是逆变的(参数 T)和协变的(返回值 R),符合 “消费者逆变,生产者协变” 原则。
- 数据容器:自定义缓存、队列等容器时,用泛型确保类型安全(如
Cache[K, V])。
- 适配器模式:通过泛型适配不同类型的输入输出,如
Converter[-From, +To]。
v1.3.10