嵌套通配符

时间:2015-01-12 12:37:44

标签: java generics wildcard

找到了令我讨厌的无界通配符的事实。例如:

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.

1 个答案:

答案 0 :(得分:2)

简短的答案是泛型是不变,因此这将无法正常工作。

长答案需要一段时间才能理解。它开始很简单:

Dog    woof   = new Dog();
Animal animal = woof; 

工作正常,因为DogAnimal。另一方面:

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

如果映射是不可变的 且编译器会关心,则可以避免这种情况,并且可以使分配工作正常。