泛型与类型参数
了解如何编写泛型类和方法,在保证类型安全的同时创建可复用组件。
泛型与类型参数 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 1 节课,共 3 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 3 节课。
什么是泛型?
假设您需要创建一个能够容纳任意类型项的容器,例如 String、Int 或自定义对象。如果没有泛型,您要么为每种类型分别编写一个容器,导致代码重复;要么使用像 Any 这样非常宽泛的类型。
泛型让您能够编写灵活且可复用的代码,使其适用于各种数据类型,同时在编译时仍提供类型安全。这就像一份蓝图,之后可以填入不同的材料!
`Any` 类型陷阱
使用 Any 作为类型占位符可能会带来问题。虽然它允许您存储任何内容,但您会丢失具体的类型信息。这意味着您经常需要将项强制转换回其原始类型;这种做法容易出错,如果转换不正确,还可能导致运行时崩溃。
请尝试运行以下示例,并思考其中可能存在的问题:
class ItemHolder(val item: Any)
object Main {
def main(args: Array[String]): Unit = {
val stringHolder = new ItemHolder("Hello Scala")
val numberHolder = new ItemHolder(123)
// We need to cast, and it's unsafe if we get it wrong
val retrievedString = stringHolder.item.asInstanceOf[String]
println(s"Retrieved String: $retrievedString")
// If we uncomment the line below, it compiles but will crash at runtime!
// val wrongType = numberHolder.item.asInstanceOf[String]
// println(s"Wrong Type: $wrongType")
}
}引入类型参数
泛型通过引入类型参数解决了 Any 类型陷阱。类型参数就像实际类型的占位符,您可以在创建类的实例或调用方法时指定实际类型。
在 Scala 中,类型参数使用方括号([])定义在类名或方法名之后,通常使用单个大写字母表示,例如 T、A、B 等。例如,class Box[T] 声明了一个泛型类 Box,它接受一个类型参数 T。
构建泛型 Box
让我们创建一个名为 Box 的简单泛型类,使其能够容纳任意类型的内容。请注意,在整个类定义中,T 都用来表示内容的类型。
这样,我们就能创建用于存放字符串、整数或其他任意类型的 Box,而不必为每种类型分别编写类。
class Box[T](val content: T) {
def get: T = content
override def toString: String = s"Box($content)"
}
object Main {
def main(args: Array[String]): Unit = {
val stringBox = new Box("Scala is fun!")
val intBox = new Box(42)
val booleanBox = new Box(true)
println(stringBox)
println(intBox)
println(booleanBox)
}
}泛型带来的类型安全
泛型最大的优势是编译时类型安全。当您创建 Box[String] 时,编译器知道其中只能包含字符串。如果您尝试向其中放入整数,或将其取出为整数,编译器会立即发现错误。
这可以避免许多使用 Any 等类型安全性较低的方法所导致的常见运行时错误。
class Box[T](val content: T) {
def get: T = content
}
object Main {
def main(args: Array[String]): Unit = {
val myStringBox: Box[String] = new Box("CoddyKit")
val myIntBox: Box[Int] = new Box(100)
// This works, type is String
val s: String = myStringBox.get
println(s"String from box: $s")
// This works, type is Int
val i: Int = myIntBox.get
println(s"Int from box: $i")
// The compiler prevents this line from compiling:
// val wrong: String = myIntBox.get // Type mismatch error!
}
}方法也可以使用泛型!
与类一样,单个方法也可以是泛型的。泛型方法可以接受仅属于该方法的类型参数,因此能够处理不同类型,而不要求整个类都是泛型的。
当某个特定操作适用于多种类型,但类本身不是泛型容器时,这种方式非常有用。
灵活的 `printPair` 方法
下面是泛型方法 printPair 的示例。它接受两个类型参数 A 和 B,因此可以打印由任意两种类型组成的对。请注意,类型参数紧跟在方法名之后声明。
这使得该方法可以高度复用于不同的数据组合。
object Util {
def printPair[A, B](first: A, second: B): Unit = {
println(s"Pair: ($first, $second)")
println(s"Type of first: ${first.getClass.getName}")
println(s"Type of second: ${second.getClass.getName}")
}
}
object Main {
def main(args: Array[String]): Unit = {
Util.printPair("Hello", 123)
Util.printPair(true, List(1, 2, 3))
Util.printPair(4.5, 'C')
}
}智能类型推断
Scala 的强大功能之一是类型推断。使用泛型类或泛型方法时,您通常不需要显式指定类型参数。Scala 编译器足够智能,可以根据您提供的参数推断出它们。
这样既能让代码更加简洁、易读,又不会牺牲类型安全。
class Container[T](val item: T) {
def getItem: T = item
}
object Main {
def main(args: Array[String]): Unit = {
// Scala infers T as String
val stringContainer = new Container("Inferred String")
println(s"Content: ${stringContainer.getItem}, Type: ${stringContainer.getItem.getClass.getName}")
// Scala infers T as Double
val doubleContainer = new Container(3.14)
println(s"Content: ${doubleContainer.getItem}, Type: ${doubleContainer.getItem.getClass.getName}")
// You can also specify explicitly, but often not needed
val explicitIntContainer: Container[Int] = new Container(500)
println(s"Content: ${explicitIntContainer.getItem}, Type: ${explicitIntContainer.getItem.getClass.getName}")
}
}使用边界限制泛型类型
有时,您希望泛型类或泛型方法只适用于具备某些能力的类型。例如,执行数字相加的方法应当只接受数值类型。
类型边界可以限制哪些类型能够用作类型参数。上界(<:)表示类型参数必须是给定类型的子类型。例如,[T <: Animal] 表示 T 必须是 Animal 或其子类型。
class Animal { def speak(): String = "..." }
class Dog extends Animal { override def speak(): String = "Woof!" }
class Cat extends Animal { override def speak(): String = "Meow!" }
// This box can only hold types that are Animal or its subtypes
class AnimalShelter[T <: Animal](val animal: T) {
def getAnimalSound: String = animal.speak()
}
object Main {
def main(args: Array[String]): Unit = {
val dogShelter = new AnimalShelter(new Dog())
println(s"Dog says: ${dogShelter.getAnimalSound}")
val catShelter = new AnimalShelter(new Cat())
println(s"Cat says: ${catShelter.getAnimalSound}")
// This would NOT compile because Car is not an Animal:
// class Car
// val carShelter = new AnimalShelter(new Car())
}
}泛型类挑战
现在来检查一下您对泛型的理解!
请仔细阅读以下陈述,并选择所有关于 Scala 泛型类和泛型方法的正确说法。
泛型:要点回顾
在本课中,我们学习了 Scala 的泛型,这是编写灵活且类型安全代码的强大功能。
- 泛型允许类和方法处理不同类型。
- 它们使用类型参数(例如
[T])作为实际类型的占位符。 - 泛型提供编译时类型安全,可以防止运行时错误。
- Scala 的类型推断通常能让泛型代码更加简洁。
- 类型边界(例如
<:)可以将类型参数限制为特定的子类型。
使用泛型,您可以编写稳健且适应性强的可复用组件,使其适用于各种数据类型,从而让 Scala 代码更加强大,也更不容易出错。
常见问题解答
「泛型与类型参数」课时是免费的吗?
是的 — 「泛型与类型参数」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 3 节课。
「泛型与类型参数」这节课中我会学到什么?
了解如何编写泛型类和方法,在保证类型安全的同时创建可复用组件。 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 3 节。
「泛型与类型参数」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?
能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。