假设我有这些课程:车辆,汽车和宇宙飞船:
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)。
答案 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());
}