MST

星途 面试题库

面试题:Kotlin类型投影在复杂泛型层级结构中的问题与解决

在一个多层级的泛型类结构中,例如`Outer<T>`,`Inner<U>`,其中`Inner`是`Outer`的内部类且`U`依赖于`T`,在这种复杂结构下,阐述如何正确使用类型投影(协变、逆变)来避免类型安全问题,并给出具体代码示例及对可能出现的错误情况分析。
27.6万 热度难度
编程语言Kotlin

知识考点

AI 面试

面试题答案

一键面试

类型投影概念

  1. 协变(Covariance):允许将一个类型参数为子类型的对象,赋值给类型参数为父类型的对象。在Java中,使用<? extends T>来表示协变。例如,如果有Animal类和Dog类(Dog继承自Animal),List<? extends Animal>可以存放Dog对象。这保证了从这个集合中读取元素时的类型安全,因为读取出来的元素至少是Animal类型。
  2. 逆变(Contravariance):允许将一个类型参数为父类型的对象,赋值给类型参数为子类型的对象。在Java中,使用<? super T>来表示逆变。例如,List<? super Dog>可以存放Dog对象,也可以存放Dog的父类型对象,这在往集合中写入元素时保证类型安全,因为写入的元素只要是Dog或其子类型即可。

多层级泛型类结构中使用类型投影

假设我们有如下多层级泛型类结构:

class Outer<T> {
    class Inner<U extends T> {
        U value;
        Inner(U value) {
            this.value = value;
        }
        U getValue() {
            return value;
        }
    }
}

协变示例

class Animal {}
class Dog extends Animal {}

public class CovarianceExample {
    public static void main(String[] args) {
        Outer<Animal> outerAnimal = new Outer<>();
        Outer.DogInner dogInner = outerAnimal.new DogInner(new Dog());
        Outer<? extends Animal>.Inner<? extends Animal> inner = dogInner;
        Animal animal = inner.getValue();
    }
}

在这个示例中,Outer<? extends Animal>.Inner<? extends Animal>使用了协变。这样可以安全地从inner对象中读取Animal类型的对象,因为Inner类的类型参数UAnimal的子类型。

逆变示例

class Animal {}
class Dog extends Animal {}

public class ContravarianceExample {
    public static void main(String[] args) {
        Outer<Dog> outerDog = new Outer<>();
        Outer.DogInner dogInner = outerDog.new DogInner(new Dog());
        Outer<? super Dog>.Inner<? super Dog> inner = dogInner;
        inner.setValue(new Dog());
    }
}

这里Outer<? super Dog>.Inner<? super Dog>使用了逆变。这允许我们向inner对象中写入Dog类型的对象,因为Inner类的类型参数UDog的父类型。

可能出现的错误情况分析

  1. 协变写入错误:如果在协变的情况下尝试写入对象,会导致编译错误。例如:
Outer<? extends Animal>.Inner<? extends Animal> inner =...;
// 以下代码会编译错误,因为不知道具体的子类型
inner.setValue(new Dog()); 

这是因为<? extends Animal>可以是Animal的任意子类型,编译器无法确定具体类型,所以不允许写入。 2. 逆变读取错误:如果在逆变的情况下尝试读取对象,会得到一个不确定类型的对象。例如:

Outer<? super Dog>.Inner<? super Dog> inner =...;
// 以下代码需要强制类型转换,可能导致运行时错误
Dog dog = (Dog) inner.getValue(); 

因为<? super Dog>可能是Dog的任意父类型,读取出来的对象不一定是Dog类型,所以直接强制转换可能会在运行时抛出ClassCastException