泛型入门
泛型创建参数化类型:在多个类型之间工作的组件。
术语“泛型”意味着“与大量类有关或适用的”。编程语言中泛型的最初目的是通过放松对那些类或函数的类型约束,为程序员在编写类或函数时提供最大的表达能力。
泛型最令人信服的最初动机之一是创建集合类,您已经在本书中的示例中看到了这些集合类,例如 List、Set 和 Map。集合是保存其他对象的对象。许多程序需要您在使用这些对象时将它们保存在一组对象中,因此集合是最可重用的类库之一。
让我们来看一个保存单个对象的类。该类指定该对象的确切类型:
// IntroGenerics/RigidHolder.kt
package introgenerics
import atomictest.eq
data class Automobile(val brand: String)
class RigidHolder(private val a: Automobile) {
fun getValue() = a
}
fun main() {
val holder = RigidHolder(Automobile("BMW"))
holder.getValue() eq
"Automobile(brand=BMW)"
}
RigidHolder 不是一个特别可重用的工具;它只能保存 Automobile。我们不希望为每种不同的类型编写一个新的持有者类型。为了实现这一点,我们使用 类型参数 代替 Automobile。
要定义一个泛型类型,请在类名之后添加包含一个或多个泛型占位符的尖括号(<>),并将这个泛型规范放在类名之后。在这里,泛型占位符 T 代表未知类型,并在类内部使用,就像它是普通类型一样:
// IntroGenerics/GenericHolder.kt
package introgenerics
import atomictest.eq
class GenericHolder<T>( // [1]
private val value: T
) {
fun getValue(): T = value
}
fun main() {
val h1 = GenericHolder(Automobile("Ford"))
val a: Automobile = h1.getValue() // [2]
a eq "Automobile(brand=Ford)"
val h2 = GenericHolder(1)
val i: Int = h2.getValue() // [3]
i eq 1
val h3 = GenericHolder("Chartreuse")
val s: String = h3.getValue() // [4]
s eq "Chartreuse"
}
- [1]
GenericHolder存储一个T,它的成员函数getValue()返回一个T。
当您调用 getValue(),如 [2]、[3] 或 [4] 所示,结果会自动是正确的类型。
看起来我们可以使用“通用类型”来解决这个问题,即一个作为所有其他类型的父类的类型。在 Kotlin 中,这个通用类型称为 Any。正如其名称所示,Any 允许任何类型的参数。如果要将多种类型的参数传递给一个函数,而这些类型之间没有任何共同之处,Any 可以解决这个问题。
乍一看,似乎我们可以在 GenericHolder.kt 中使用 Any 替代 T 来解决这个问题:
// IntroGenerics/AnyInstead.kt
package introgenerics
import atomictest.eq
class AnyHolder(private val value: Any) {
fun getValue(): Any = value
}
class Dog {
fun bark() = "Ruff!"
}
fun main() {
val holder = AnyHolder(Dog())
val any = holder.getValue()
// 不编译:
// any.bark()
val genericHolder = GenericHolder(Dog())
val dog = genericHolder.getValue()
dog.bark() eq "Ruff!"
}
实际上,Any 对于简单的情况确实有效,但是一旦我们需要特定的类型——调用 Dog 的 bark() 方法时,它就不起作用了,因为当它被分配给 Any 时,我们失去了它是 Dog 的事实。当我们将 Dog 传递为 Any 时,结果只是一个 Any,它没有 bark() 方法。
使用泛型保留了这个信息,在这种情况下,我们实际上有一个 Dog,这意味着我们可以对 getValue() 返回的对象执行 Dog 操作。
泛型函数
要定义一个泛型函数,请在函数名之前的尖括号中指定一个泛型类型参数:
// IntroGenerics/GenericFunction.kt
package introgenerics
import atomictest.eq
fun <T> identity(arg: T): T = arg
fun main() {
identity("Yellow") eq "Yellow"
identity(1) eq 1
val d: Dog = identity(Dog())
d.bark() eq "Ruff!"
}
d 的类型为 Dog,因为 identity() 是一个泛型函数,并返回一个 T。
Kotlin 标准库包含许多用于集合的泛型扩展函数。要编写一个泛型扩展函数,请将泛型规范放在接收者之前。例如,注意 first() 和 firstOrNull() 是如何定义的:
// IntroGenerics/GenericListExtensions.kt
package introgenerics
import atomictest.eq
fun <T> List<T>.first(): T {
if (isEmpty())
throw NoSuchElementException("Empty List")
return this[0]
}
fun <T> List<T>.firstOrNull(): T
? =
if (isEmpty()) null else this[0]
fun main() {
listOf(1, 2, 3).first() eq 1
val i: Int? = // [1]
listOf(1, 2, 3).firstOrNull()
i eq 1
val s: String? = // [2]
listOf<String>().firstOrNull()
s eq null
}
first() 和 firstOrNull() 可以与任何类型的 List 一起工作。为了返回一个 T,它们必须是泛型函数。
注意,firstOrNull() 指定了一个可空的返回类型。[1] 行显示,在 List<Int> 上调用该函数返回可空类型 Int?。[2] 行显示,在 List<String> 上调用 firstOrNull() 返回 String?。Kotlin 要求在 [1] 和 [2] 行上使用 ?,去掉它们并查看错误消息。
练习和答案可以在 www.AtomicKotlin.com 找到。