找到了令我讨厌的无界通配符的事实。例如:
public class Test {
private static final Map<Integer, Map<Integer, String>> someMap = new HashMap<>();
public static void main(String[] args) {
getSomeMap();
}
static Map<?, Map<?, ?>> getSomeMap() {
return someMap; //compilation fails
}
}
虽然可以使用Map<?, ?>
或Map<?, Map<Integer, String>>
返回类型,但却失败了。
有人可以告诉我确切的原因吗?提前谢谢。
更新
在我看来,似乎我理解并且对这个问题的最简单解释(省略所有这些复杂的规则)是Capture Conversion(link)中的最后一个注释:Capture conversion is not applied recursively.
答案 0 :(得分:2)
简短的答案是泛型是不变,因此这将无法正常工作。
长答案需要一段时间才能理解。它开始很简单:
Dog woof = new Dog();
Animal animal = woof;
工作正常,因为Dog
是Animal
。另一方面:
List< Animal > fauna = new ArrayList<>();
List< Dog > dogs = new ArrayList<>();
fauna = dogs;
将无法编译,因为泛型是不变的。基本上List<Dog>
不是 List<Animal>
。
为什么?好吧,如果可以完成分配任务,那么您将无法进行以下操作:
fauna.add(new Cat());
dogs.get(0); // what is this now?
实际上,这里的编译器可能更聪明。如果您的列表不可变怎么办?创建后,您不能在其中添加任何内容。在这种情况下,应允许使用fauna = dogs
,但是java不会这样做(scala不会这样做),即使Java-9中新添加了Immutable集合也是如此。
当列表是不可变的时,它们被称为Producers
,这意味着它们不会将通用类型作为输入。例如:
interface Sink<T> {
T nextElement();
}
由于Sink
从不接受T
作为输入,因此它是Producer
中的T
(不是Consumer
),因此有可能说:
Sink<Object> objects ...
Sink<String> strings ...
objects = strings;
由于Sink
没有 add 元素的选择,因此我们无法破坏任何东西,但是java不在乎并禁止这样做。 kotlinc
(就像scalac
一样)允许。
在Java中,此缺陷通过“有界类型”解决:
List<? extends Animal> animals = new ArrayList<>();
animals = dogs;
好处是您仍然无法执行:animals.add(new Cat())
。您确切地知道该列表所包含的内容-某些类型的动物,因此,当您阅读该列表时,实际上,您始终会知道自己会获得Animal
。但是因为例如List<? extends Animal>
可分配给List<Dog>
,所以禁止加法,否则:
animals.add(new Cat()); // if this worked
dogs.get(0); // what is this now?
此“禁止添加”并不完全正确,因为始终可以这样做:
private static <T> void topLevelCapture(List<T> list) {
T t = list.get(0);
list.add(t);
}
topLevelCapture(animals);
here解释了为什么这样做有效,重要的是这不会破坏任何内容。
如果您想说自己有一组动物,例如List<List...>
,该怎么办?可能要做的第一件事是List<List<Animal>>
:
List<List<Animal>> groups = new ArrayList<>();
List<List<Dog>> dogs = new ArrayList<>();
groups = dogs;
这显然行不通。但是,如果我们添加有界类型怎么办?
List<List<? extends Animal>> groups = new ArrayList<>();
List<List<Dog>> dogs = new ArrayList<>();
groups = dogs;
即使List<Dog>
是 List<? extends Animal>
,它们的泛型也不是 (泛型是不变的)。同样,如果允许这样做,则可以执行以下操作:
groups.add(<list of cats>);
dogs.get(0); // obvious problems
使其生效的唯一方法是通过:
List<? extends List<? extends Animal>> groups = new ArrayList<>();
List<List<Dog>> dogs = new ArrayList<>();
groups = dogs;
也就是说,我们在List<Dog>
中找到了List<? extends Animal>
的超类型,我们还需要有界的类型? extends List...
,以便外部列表本身是可分配的。
此巨大简介旨在显示:
Map<Integer, Map<Integer, String>> map = new HashMap<>();
Map<?, ?> broader = new HashMap<>();
broader = map;
因为没有任何限制,所以将进行编译,broader
映射基本上是“任何内容”的映射。
如果您阅读了我上面要说的话,您可能知道为什么不允许这样做:
Map<Integer, Map<Integer, String>> map = new HashMap<>();
Map<?, Map<?, ?>> lessBroader = new HashMap<>();
lessBroader = map;
如果允许的话,您可以这样做:
Map<Double, Float> newMap = new HashMap<>(); // this is a Map<?, ?> after all
lessBroader.add(12, newMap);
map.get(12); // hmm...
如果映射是不可变的 且编译器会关心,则可以避免这种情况,并且可以使分配工作正常。