编程后端开发工程师

Kotlin中是如何实现泛型的?存在哪些限制,如何工作不变性、协变性和逆变性,它们与Java中的泛型有何不同?请举例说明。

用 Hintsage AI 助手通过面试

答案

泛型在Kotlin中允许创建通用和类型安全的数据结构和函数。主要特点:Kotlin在编译时实现了泛型类型系统,与Java相似,但具有更严格的类型检查和扩展的变异性语法(协变性和逆变性)。

泛型的限制:

  • 类型参数默认是不可变的。
  • 不能创建类型参数的实例(T() 是禁止的)。
  • 在运行时没有对泛型类型信息的访问(类型擦除)。

变异性:

  • 协变性 (out T): 允许使用子类型。
  • 逆变性 (in T): 允许使用超类型。
  • 不变性: 没有变异性修饰符的类型。

协变性的例子:

interface Producer<out T> { fun produce(): T }

逆变性的例子:

interface Consumer<in T> { fun consume(item: T) }

与Java的区别:

  • 语法更加明确和简洁(out 替代 ? extendsin 替代 ? super)。
  • 没有通配符类型(?,只有 in/out)。
  • 不允许创建泛型参数的实例。

试探性问题

问题: "在Kotlin中能否将数组数组(Array<Array<Int>>)声明为Array<out Array<Int>>,并且在尝试向该数组写入时会发生什么?"

答案: 是的,可以声明为 Array<out Array<Int>>,但这样的数组变为只读(read-only):

val arr: Array<out Array<Int>> = Array(1) { Array(1) { 0 } } arr[0] = arrayOf(1, 2, 3) // 编译错误!

尝试写入值将导致错误——因为带有 out 参数的泛型数组不允许写入元素,因为这将破坏类型安全。

由于对该主题的细微差别不了解而导致的实际错误示例


故事

团队尝试创建一个带有 out 参数类型的泛型对象数组,然后通过 set(index, value) 向其中放入值。代码编译通过,但在运行时引发错误,导致多个函数不可用。


故事

一次在将库从Java迁移到Kotlin时,保留了通配符类型(? extends ...),而在Kotlin中只是复制了类型而没有修改为out/in。结果是——编译未通过,而在 "遍历" 时错误在运行时爆发,增加了调试的复杂性。


故事

使用in/out变异性与用户定义的类,但将修饰符搞混了,声明了接口Stack<in T>而不是Stack<out T>。这导致无法从栈中返回元素:方法的签名违反了系统的out/in契约。