JavagenericsWildCard问题:List <? 扩展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的超types。 对?

谢谢

编辑:我得到的错误(编译时):types列表中的方法添加(捕获#2的?扩展车辆)不适用于参数(汽车)。

方法参数在子types中是不相容的,通过通配符的定义,对于每一个扩展了VehicletypesTFoo<T>Foo<* extends Vehicle>的子types。 这意味着当你只关心返回types时,通配符是很好的,但是当你想把types的值传递给一个方法时,在这种情况下就不会工作。

问题是用户可能试图打电话

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

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

这里是一个指向你为什么得到一个编译错误的指针。 特别,

列表是一个有界通配符的例子。 这个? 代表了一个未知的types,就像我们之前看到的通配符。 然而,在这种情况下,我们知道这个未知types实际上是Shape的一个子types。 (注意:它可以是Shape本身,也可以是一些子类,它不需要从字面上扩展Shape。)我们说Shape是通配符的上界。

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

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

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

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

应该

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

这将修复编译时错误。

编辑: 在这里阅读。

提供的List是一些特定types的Vehicle的列表(其中,为了论证,我们将把types称为T ),但是具体的typesT是未知的; 它可以是List<Vehicle>List<Car>等。因此,由于列表的特定genericstypes是未知的,所以不允许调用任何需要特定T作为参数的方法。 只有不涉及T作为参数的方法才能被调用。

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

也就是说,未知的typesT不能被提供给List,但是已知的Vehicle超类可以被列表返回。

举个例子,给你的方法:

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

你可以想象地调用:

 List<Car> cars=new ArrayList<Car>(); addCars(cars); 

你应该允许你的直觉。 但是,由于addCars仅将列表知道为“ Vehicle某个子types”,所以不能将对象添加到列表中,因为以下调用将同样有效:

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

由此可以明显地看出,试图将Car对象作为Vehicle对象列表的幌子添加到列表中一定是错误的。

参数的types是? extends Vehicle ? extends Vehicle ,这意味着? extends Vehicle的未知子types。 既然我们不知道它是什么types,我们不知道它是否是Car的超types; 它可能会也可能不是这样一个超types,所以在那里通过一辆Car是不安全的。

阅读本教程的第7页。

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

你可以使用:

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

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

要么

 private static void addCars(List<Vehicle> vcls) 

如果以下可能..

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

获取和放置原则问题:

你可以试试这个

 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 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

使用王子获取并放入通配符如果通配符与Extends —>使用get方法如果通配符与Super —->使用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()); }