0%

scala泛型

Scala 泛型:类型安全与灵活性的平衡

泛型是 Scala 中实现类型抽象的核心机制,它允许在定义类、特质、方法时使用类型参数,从而编写与具体类型无关的通用代码。Scala 泛型不仅支持类似 Java 的上下界约束,还引入了协变、逆变等独特特性,进一步增强了类型系统的灵活性。本文将全面解析 Scala 泛型的用法与高级特性。

泛型基础:类型参数的定义与使用

Scala 泛型使用方括号 [] 定义类型参数,可用于类、特质、方法等,实现代码的复用与类型安全。

泛型类

在类定义中声明类型参数,使类能处理多种类型的数据:

1
2
3
4
5
6
7
8
9
10
11
12
// 定义泛型类 Box,类型参数为 T
class Box[T](val content: T) {
// 方法返回值使用类型参数 T
def getContent: T = content
}

// 创建不同类型的 Box 实例
val intBox = new Box[Int](10)
val strBox = new Box[String]("hello")

println(intBox.getContent) // 输出:10(类型为 Int)
println(strBox.getContent) // 输出:hello(类型为 String)

泛型方法

在方法中声明类型参数,使方法能独立于类的类型参数处理不同类型:

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(",")) // 输出:3,2,1

val strArray = Array("a", "b", "c")
GenericUtils.swap(strArray, 1, 2)
println(strArray.mkString(",")) // 输出:a,c,b

类型边界:限制泛型的范围

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
// 定义父类 Animal
class Animal(val name: String) {
def eat: Unit = println(s"$name 正在吃东西")
}

// 子类 Dog
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]) {
// 调用 Animal 的方法(因 T 是 Animal 子类型,确保有 eat 方法)
def feedAll(): Unit = pets.foreach(_.eat)
}

// 创建 PetShop 实例(只能传入 Dog 或 Cat 等 Animal 子类型)
val dogShop = new PetShop[Dog](List(new Dog("Buddy"), new Dog("Max")))
dogShop.feedAll()
// 输出:
// Buddy 正在吃东西
// Max 正在吃东西

说明T <: Animal 确保 TAnimal 的子类型,因此 PetShop 中可安全调用 Animal 的方法(如 eat)。

下界(>:

下界限制类型参数必须是指定类型的父类型(类似 Java 的 super)。

1
2
3
4
5
6
7
8
9
10
11
12
// 泛型方法:向列表添加元素,元素类型必须是 T 的父类型
def addElement[T](list: List[T], elem: T): List[T] = elem :: list

// 下界示例:将 Dog 转换为 Animal 后添加到 Animal 列表
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))
// 输出:
// Buddy
// Mimi

说明T >: Dog 确保 TDog 的父类型(如 Animal),因此 Dog 实例可安全添加到 List[T] 中。

协变、逆变与不变:泛型类型的继承关系

Scala 泛型的独特之处在于支持协变(Covariance)逆变(Contravariance),用于描述泛型类型之间的继承关系。这一特性解决了 Java 中 List<String> 不是 List<Object> 子类型的问题。

基本概念

假设有类型 C[T] 和两个类 AB,其中 AB 的子类型(A <: B):

  • 协变(C[+T]C[A] <: C[B]C[A]C[B] 的子类型,与 AB 的关系一致)。
  • 逆变(C[-T]C[B] <: C[A]C[B]C[A] 的子类型,与 AB 的关系相反)。
  • 不变(C[T]C[A]C[B] 无继承关系(默认行为)。

协变(+T

协变适用于生产者类型(只返回 T,不消费 T),如只读集合。

1
2
3
4
5
6
7
8
9
10
11
// 协变类:List 是 Scala 中典型的协变类型(定义为 List[+T])
val dogs: List[Dog] = List(new Dog("Buddy"))
val animals: List[Animal] = dogs // 合法:List[Dog] 是 List[Animal] 的子类型(因协变)

// 自定义协变类(生产者)
class ReadOnlyBox[+T](val content: T) {
def get: T = content // 只返回 T(生产 T)
}

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
// 逆变类:定义一个动物处理器,只消费 T(接收 T 作为参数)
class AnimalHandler[-T] {
def handle(animal: T): Unit = println(s"处理动物:${animal.name}")
}

// AnimalHandler[Animal] 可以处理所有动物,包括 Dog
val animalHandler: AnimalHandler[Animal] = new AnimalHandler[Animal]()
val dogHandler: AnimalHandler[Dog] = animalHandler // 合法:逆变(AnimalHandler[Animal] 是 AnimalHandler[Dog] 的子类型)

dogHandler.handle(new Dog("Buddy")) // 输出:处理动物:Buddy

为什么逆变合理?
AnimalHandler[Animal] 能处理所有 Animal(包括 Dog),因此它可以安全地作为 AnimalHandler[Dog] 使用(专门处理 Dog)。

不变(默认)

如果泛型类既生产又消费 T,则必须是不变的,否则会导致类型不安全。

1
2
3
4
5
6
7
8
9
// 不变类:既生产又消费 T
class MutableBox[T](var content: T) {
def get: T = content // 生产 T
def set(value: T): Unit = content = value // 消费 T
}

val dogBox: MutableBox[Dog] = new MutableBox(new Dog("Buddy"))
// 编译错误:MutableBox 是不变的,MutableBox[Dog] 不是 MutableBox[Animal] 的子类型
// val animalBox: MutableBox[Animal] = dogBox

为什么禁止协变?
若允许 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
// 通配符上界:接收任意 Animal 子类型的列表
def printAnimals(list: List[_ <: Animal]): Unit = {
list.foreach(animal => println(animal.name))
}

// 通配符下界:接收任意 Dog 父类型的列表
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) // 合法:Dog <: Animal
printAnimals(cats) // 合法:Cat <: Animal

val animals = List(new Animal("Generic Animal"))
val newList = addDogToList(animals) // 合法:Animal >: Dog

泛型的应用场景

  1. 集合框架:Scala 集合(如 ListSetMap)广泛使用泛型,List[+T] 是协变的,Map[K, +V] 中值类型是协变的。
  2. 函数类型:Scala 函数类型 Function1[-T, +R] 是逆变的(参数 T)和协变的(返回值 R),符合 “消费者逆变,生产者协变” 原则。
  3. 数据容器:自定义缓存、队列等容器时,用泛型确保类型安全(如 Cache[K, V])。
  4. 适配器模式:通过泛型适配不同类型的输入输出,如 Converter[-From, +To]

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10