MST

星途 面试题库

面试题:Kotlin泛型与协变、逆变的深度剖析

在Kotlin中,协变(`out`关键字)和逆变(`in`关键字)是泛型编程中的重要概念。请详细解释协变和逆变的原理,并通过具体的代码示例说明它们在函数参数、返回值以及类型兼容性方面的表现。此外,分析在设计一个可复用的库时,如何正确运用协变和逆变来提高代码的灵活性和安全性。
31.0万 热度难度
编程语言Kotlin

知识考点

AI 面试

面试题答案

一键面试

协变(out关键字)原理

  1. 定义:协变允许一个泛型类型参数在类型层次结构中以与原始类型相同的方向进行转换。使用out关键字标记泛型类型参数,表明该类型参数仅用于输出位置(如返回值)。
  2. 类型兼容性:如果AB的子类型,那么List<A>就是List<B>的子类型(在使用协变的情况下)。

代码示例 - 协变在函数参数、返回值方面的表现

// 定义一个协变的泛型类
class BoxOut<out T>(val value: T) {
    fun getValue(): T = value
    // 以下代码会报错,因为T是协变的,不能用于输入位置
    // fun setValue(newValue: T) { value = newValue }
}

// 函数接受一个BoxOut类型的参数,这里体现了协变的类型兼容性
fun printValue(box: BoxOut<Any>) {
    println(box.getValue())
}

fun main() {
    val boxOfString = BoxOut("Hello")
    // 可以将BoxOut<String>传递给期望BoxOut<Any>的函数
    printValue(boxOfString)
}

逆变(in关键字)原理

  1. 定义:逆变允许一个泛型类型参数在类型层次结构中以与原始类型相反的方向进行转换。使用in关键字标记泛型类型参数,表明该类型参数仅用于输入位置(如函数参数)。
  2. 类型兼容性:如果AB的子类型,那么List<B>就是List<A>的子类型(在使用逆变的情况下)。

代码示例 - 逆变在函数参数、返回值方面的表现

// 定义一个逆变的泛型类
class BoxIn<in T>(val value: T) {
    // 只能用于输入位置
    fun printValue() {
        println(value)
    }
    // 以下代码会报错,因为T是逆变的,不能用于输出位置
    // fun getValue(): T = value
}

// 函数接受一个BoxIn类型的参数,这里体现了逆变的类型兼容性
fun setValue(box: BoxIn<String>) {
    box.printValue()
}

fun main() {
    val boxOfAny = BoxIn<Any>(Any())
    // 可以将BoxIn<Any>传递给期望BoxIn<String>的函数
    setValue(boxOfAny)
}

在设计可复用库时运用协变和逆变提高灵活性和安全性

  1. 提高灵活性
    • 协变:在库中,如果某个类主要用于向外提供数据(如数据容器),使用协变可以允许用户将该容器视为其父类型的容器,从而在不同类型层次结构间灵活转换。例如,一个图片加载库返回的图片数据容器,使用协变可以让用户将其作为更通用的图像数据类型容器来处理。
    • 逆变:当库中的类主要用于接受外部数据(如数据处理器),使用逆变可以允许用户传入其子类型的数据,增强了对不同具体类型数据处理的灵活性。例如,一个文本处理库的处理器,使用逆变可以接受不同类型的文本输入(如String及其子类)。
  2. 提高安全性
    • 协变:通过限制协变类型参数只能用于输出位置,避免了将子类型数据错误地赋值给父类型的情况,保证了数据类型的安全性。例如在上述BoxOut类中,不能将BoxOut<String>setValue方法传入Any类型值,防止类型不匹配错误。
    • 逆变:通过限制逆变类型参数只能用于输入位置,避免了从父类型数据中错误地获取子类型数据的情况。例如在BoxIn类中,不能从BoxIn<Any>获取String类型值,防止类型转换错误。