2011-03-31 91 views
16

比方说,我有这些类:汽车,汽车和飞船:Java泛型通配符问题:列表<? extends A>

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的超类型。对?

感谢

编辑:我得到的错误(编译时间):Add方法(?捕获#车辆2的延伸)在类型列表中是不适用的参数(车)。

+0

可以共享编译错误?乍一看,这看起来应该工作... – 2011-03-31 04:42:35

+0

@Mark Elliot:编辑我的问题,编译错误 – 2011-03-31 04:43:44

回答

20

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

的问题是,用户可能会尝试拨打

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

如果你的代码是编译,l然后将含有3辆汽车飞船的列表。显然没有好处。

1

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

阅读this tutorial的第7页。

1

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

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

应该

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

这将解决编译时错误。编辑:阅读here

+0

谢谢我知道,但我很困惑,为什么第一个不工作 – 2011-03-31 04:58:09

+0

@Saher:'?延伸“意味着只得到。唯一的例外是你可以设置null。 – 2011-03-31 05:01:11

1

既可以使用:

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

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

private static void addCars(List<Vehicle> vcls) 
7

Here's一个指向你为什么会遇到编译错误的指针。具体而言,

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

像往常一样,通过支付 的价格,可以灵活地使用 通配符。这个价格是 现在非法写入形状 该方法的主体。例如, 这是不允许的:

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

您应该 能够找出为什么上面 的代码是不允许的。 shapes.add()的第二个参数 的类型是?延伸 形状 - Shape的未知子类型。 由于我们不知道它是什么类型,我们不知道它是否是超类型 矩形;它可能或可能不是 这样的超类型,所以 在那里传递一个Rectangle是不安全的。

5

所提供的列表是一些特定类型的车辆的列表(其中,对于参数,我们将参考类型T的缘故),但特定类型T是未知的;它可能是List<Vehicle>,List<Car>等。因此,由于该列表的特定通用类型是未知的,因此不允许调用任何需要特定的T作为参数的方法。只有不涉及T作为参数的方法才能被调用。

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

也就是说,未知类型T无法提供给列表,但其已知的超类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); 

由此变得清晰尝试将Car对象添加到列表中是不对的,因为它是一个Vehicle对象列表。

0

如果下面可能是可能的..

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); 
0

get和put原则问题:

你可以试试这个

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

-1

使用王子get和put在通配符 如果通配符与扩展--->使用GET方法 如果使用通配符与超级----> put方法 在这里,你要添加值到列表(意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()); 
    }