面试题答案
一键面试类型投影概念
- 协变(Covariance):允许将一个类型参数为子类型的对象,赋值给类型参数为父类型的对象。在Java中,使用
<? extends T>
来表示协变。例如,如果有Animal
类和Dog
类(Dog
继承自Animal
),List<? extends Animal>
可以存放Dog
对象。这保证了从这个集合中读取元素时的类型安全,因为读取出来的元素至少是Animal
类型。 - 逆变(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
类的类型参数U
是Animal
的子类型。
逆变示例
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
类的类型参数U
是Dog
的父类型。
可能出现的错误情况分析
- 协变写入错误:如果在协变的情况下尝试写入对象,会导致编译错误。例如:
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
。