Java Generics WildCard问题:列表<! - ?扩展A - >

时间:2011-03-31 04:39:03

标签: java generics wildcard

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

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是扩展Vehicle的任何X的List的超类型。正确?

由于

编辑:我得到的错误(编译时):类型List中的方法add(capture#2-of?extends Vehicle)不适用于参数(Car)。

10 个答案:

答案 0 :(得分:21)

方法参数在子类型中是逆变的,并且通过通配符的定义,对于扩展T Vehicle的每个类型Foo<T>,都是Foo<* extends Vehicle>的子类型。这意味着当你只关心返回类型时,通配符很棒,但是当你想要将类型的值传递给方法时,不能在这种情况下工作。

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

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

如果你的代码要编译,那么l就是包含3辆汽车的宇宙飞船列表。显然没有好处。

答案 1 :(得分:8)

Here's指向您收到编译错误的原因。具体地,

  

列表就是一个例子   一个有界的通配符。的?代表   一个未知的类型,就像   我们之前看到的通配符。但是,在   这种情况,我们知道这个未知数   type实际上是Shape的子类型。   (注意:它可能是Shape本身,或者   一些子类;它不需要字面意思   延伸Shape。)我们说Shape就是   通配符的上限。

     

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

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

你应该   能够弄清楚为什么上面的代码   是不被允许的。第二种类型   shapes.add()的参数是?扩展   形状 - 形状的未知子类型。   因为我们不知道它是什么类型,   我们不知道它是否是超类型   长方形;它可能会也可能不会   这样的超类型,所以它是不安全的   在那里传递一个矩形。

答案 2 :(得分:6)

提供的List是一些特定类型的Vehicle的列表(为了参数,我们将类型称为T),但该特定类型T未知;它可能是List<Vehicle>List<Car>等。因此,由于列表的特定泛型类型是未知的,因此不允许调用任何需要特定T作为参数的方法。只能调用不涉及T作为参数的方法。

在List的情况下,这样做的实际结果是,这可以防止将任何内容添加到列表中 - 列表不可写。另一方面,可以读取列表,但返回的对象只能称为Vehicle

也就是说,未知类型T无法提供给List,但列表可以返回其已知的Vehicle超类。

举个例子,给出你的方法:

private static void addCars(List<? extends Vehicle> vcls) {

你可以想象一下:

List<Car> cars=new ArrayList<Car>();
addCars(cars);
你应该允许直觉。但是,由于addCars仅将列表视为“Vehicle的某个子类型”,因此不允许将对象添加到列表中,因为以下调用将同样有效:

List<Spaceship> ships=new ArrayList<Spaceship>();
addCars(ships);

因此,很明显,在以Vehicle对象列表为幌子的情况下尝试将Car对象添加到List中一定是错误的。

答案 3 :(得分:5)

private static void addCars(List<? extends Vehicle> vcls){

应该是

private static void addCars(List<? super Vehicle> vcls){

这将修复编译时错误。

编辑:阅读here

答案 4 :(得分:2)

参数类型为? extends Vehicle,表示未知的子类型 Vehicle。由于我们不知道它是什么类型,我们不知道它是否是超类型 Car;它可能是也可能不是这样的超类型,所以通过它是不安全的 Car那里。

阅读this tutorial的第7页。

答案 5 :(得分:2)

当你说<? extends Vehicle>时,它意味着它可以是任何扩展车辆的类型。这意味着有人可以通过List,它会接受它。现在List<Spaceship>不能将新的Car()作为他的项目之一。因此,要避免这些错误,如果使用了通配符表达式,则不允许在列表中添加任何对象。

答案 6 :(得分:2)

您可以使用:

private static void addCars(List<? super Vehicle> vcls)

(这意味着调用者要传递Vehicle或超类型的对象列表)

private static void addCars(List<Vehicle> vcls)

答案 7 :(得分:1)

如果可以追随......

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

然后你可以用这种方式调用addCars:

List<SpaceShip> ships = new ArrayList<SpaceShip>();
addCars(ships);

答案 8 :(得分:1)

获取和放置原则问题:

你可以试试这个

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

就像;

List<Integer> ints=Arrays.asList(1,2,3); List<? extends Number> nums=ints; double dbl=sum(nums); // ===ok nums.add(3.14); //===compile-time-error

List<Object> ints=Arrays<Object>.asList(1,"two"); List<? super Integer> nums=ints; double dbl=sum(nums); // ===compile-time-error nums.add(3.14); //===ok

的通配符

答案 9 :(得分:0)

使用prince获取并放入通配符 如果带有Extends的通配符---&gt;使用get方法 如果带有Super的通配符----&gt;使用put方法 在这里,您想要将值添加到List(意思是put方法)。您可以更改代码

List<? extends Vehicle become List<? super Vehicle> then it will compile legally
private static void addCars(List<? super Vehicle> vcls){
        vcls.add(new Car());
        vcls.add(new Car());
        vcls.add(new Car());
    }