Java 泛型通配符问题:列表<?扩展 A>

2022-09-02 23:25:43

假设我有这些类:车辆,汽车和宇宙飞船:

class Vehicle{

    void rideVehicle(Vehicle v){
        System.out.println("I am riding a vehicle!");
    }

}

class Car extends Vehicle{
    void rideVehicle(Vehicle c){
        System.out.println("I am riding a car!");
    }
}


class SpaceShip extends Vehicle{
    void rideVehicle(Vehicle c){
        System.out.println("I am riding a spaceship!");
    }

}

我写这个方法 addCars:

private static void addCars(List<? extends Vehicle> vcls){
        vcls.add(new Car());
        vcls.add(new Car());
        vcls.add(new Car());

    }

为什么我会收到编译时错误??我知道List是任何扩展车辆的X的List的超类型。右?

谢谢

编辑:我得到的错误(编译时):类型列表中的方法add(捕获#2-of?扩展车辆)不适用于参数(Car)。


答案 1

方法参数在子类型中是逆变的,并且根据通配符的定义,对于扩展的每个类型都是 的子类型。这意味着,当您只关心返回类型时,通配符非常有用,但在这样的情况下,当您想要将该类型的值传递给方法时,通配符不起作用。TVehicleFoo<T>Foo<* extends Vehicle>

问题是用户可能会尝试调用

List<SpaceShip> l = ...
addCars(l);

如果你的代码要编译,那么将是一个包含3辆车的宇宙飞船列表。显然不好。l


答案 2

下面是一个指针,指向为什么会出现编译错误。具体说来

列表是有界通配符的一个示例。这?代表未知类型,就像我们之前看到的通配符一样。但是,在这种情况下,我们知道这种未知类型实际上是Shape的子类型。(注意:它可以是 Shape 本身,也可以是某个子类;它不需要从字面上扩展 Shape。我们说形状是通配符的上限。

像往常一样,使用通配符的灵活性需要付出代价。这个代价是,现在在方法的主体中写入形状是非法的。例如,这是不允许的:

public void addRectangle(List<? extends Shape> shapes) {
     shapes.add(0, new Rectangle()); // Compile-time error! 
} 

您应该能够弄清楚为什么不允许上述代码。shapes.add() 的第二个参数的类型是 ?扩展 Shape -- Shape 的未知子类型。由于我们不知道它是什么类型,我们不知道它是否是矩形的超类型;它可能是也可能不是这样的超类型,因此在那里传递矩形是不安全的。


推荐