泛型入门
泛型创建参数化类型:在多个类型之间工作的组件。
术语“泛型”意味着“与大量类有关或适用的”。编程语言中泛型的最初目的是通过放松对那些类或函数的类型约束,为程序员在编写类或函数时提供最大的表达能力。
泛型最令人信服的最初动机之一是创建集合类,您已经在本书中的示例中看到了这些集合类,例如 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 找到。