面试题答案
一键面试设计思路
- 协方差:在读取数据时,我们希望能够以更宽泛的类型读取数据,这时候可以使用协方差。协方差允许我们将一个类型参数声明为支持更具体类型的泛型类型。例如,如果我们有一个
List<Derived2>
,我们希望在读取操作中可以将其视为List<Base>
。在Kotlin中,使用out
关键字来声明协变类型参数。 - 反协方差:在写入数据时,我们希望能够以更具体的类型写入数据,这时候可以使用反协方差。反协方差允许我们将一个类型参数声明为支持更宽泛类型的泛型类型。例如,如果我们有一个
List<Base>
,我们希望在写入操作中可以将Derived2
类型的数据写入其中。在Kotlin中,使用in
关键字来声明逆变类型参数。
对于多层嵌套的泛型类,我们需要根据不同的操作(读或写),在合适的层次上应用协方差和反协方差。
Kotlin代码示例
// 定义基类和继承类
open class Base
class Derived1 : Base()
class Derived2 : Derived1()
// 外层泛型类,使用协方差支持读取操作
class Outer<out T> {
// 内层泛型类,使用协方差支持读取操作
inner class Inner<out U> {
// 成员变量使用协方差支持读取操作
val list: List<out V> where V : Base
constructor(list: List<out V> where V : Base) {
this.list = list
}
}
constructor(inner: Inner<U>)
}
// 用于写入操作的泛型类,使用反协方差
class WriteOuter<in T> {
inner class WriteInner<in U> {
val writeList: MutableList<in V> where V : Base
constructor(writeList: MutableList<in V> where V : Base) {
this.writeList = writeList
}
fun write(item: V) where V : Base {
writeList.add(item)
}
}
constructor(writeInner: WriteInner<U>)
}
fun main() {
// 读取操作示例
val derived2List: List<Derived2> = listOf(Derived2())
val outerRead: Outer<Base> = Outer(Outer.Inner(derived2List))
val readItem: Base? = outerRead.Inner.list.firstOrNull()
// 写入操作示例
val baseList: MutableList<Base> = mutableListOf()
val outerWrite: WriteOuter<Base> = WriteOuter(WriteOuter.WriteInner(baseList))
outerWrite.WriteInner.write(Derived2())
}
注释说明
- 基类和继承类:定义了
Base
类以及它的两个子类Derived1
和Derived2
,用于展示类型继承关系。 - 外层泛型类
Outer
:使用out
关键字声明类型参数T
,表示协变,支持读取操作。 - 内层泛型类
Inner
:同样使用out
关键字声明类型参数U
,且list
成员变量使用out
关键字声明类型参数V
,并且限定V
必须是Base
或其子类,以支持读取操作。 - 写入操作相关类
WriteOuter
和WriteInner
:使用in
关键字声明类型参数,支持写入操作。writeList
是一个可变列表,允许将Base
或其子类的数据写入。write
方法用于向列表中添加数据。 main
函数:展示了读取和写入操作的示例。在读取操作中,创建了一个List<Derived2>
,并将其用于创建Outer<Base>
的实例,成功读取数据。在写入操作中,创建了一个MutableList<Base>
,并将其用于创建WriteOuter<Base>
的实例,成功向列表中写入Derived2
类型的数据。