Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

泛型入门

泛型创建参数化类型:在多个类型之间工作的组件。

术语“泛型”意味着“与大量类有关或适用的”。编程语言中泛型的最初目的是通过放松对那些类或函数的类型约束,为程序员在编写类或函数时提供最大的表达能力。

泛型最令人信服的最初动机之一是创建集合类,您已经在本书中的示例中看到了这些集合类,例如 ListSetMap。集合是保存其他对象的对象。许多程序需要您在使用这些对象时将它们保存在一组对象中,因此集合是最可重用的类库之一。

让我们来看一个保存单个对象的类。该类指定该对象的确切类型:

// 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 对于简单的情况确实有效,但是一旦我们需要特定的类型——调用 Dogbark() 方法时,它就不起作用了,因为当它被分配给 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 找到。