双通配符泛型类型错误

3

我正在尝试为我的自定义 Map 类型定义一个操作符 ++,如下所示:

@Override
public MutableMap<K, V> $plus$plus(Map<? extends K, ? extends V> map)
{
    HashMap<K, V> copy = this.copy();
    map.$plus$plus$eq(map);
    return copy;
}
++= 运算符的定义如下:
public void $plus$plus$eq(Map<? extends K, ? extends V> map);

然而,编译器在map.$plus$plus$eq(map);这行报错,并显示下面疯狂的错误信息:
The method $plus$plus$eq(Map<? extends capture#10-of ? extends K,
? extends capture#11-of ? extends V>) in the type
Map<capture#10-of ? extends K,capture#11-of ? extends V> is not
applicable for the arguments
(Map<capture#12-of ? extends K,capture#13-of ? extends V>)

正如您在屏幕截图中看到的那样,Eclipse 提供的解决方案没有一个起作用,更不用说有意义了。
我一直在使用 Java 泛型编程,甚至为自己的自定义编程开发了自己的泛型类型系统(我目前正在编写该库),但我从未遇到过这样的错误。
编辑:有趣的是,将“map”参数强制转换为原始类型“(Map)”似乎可以解决问题。
map.$plus$plus$eq((Map) map);

然而,将cast更改为 (Map<?, ?>)(这是Eclipse的第二种解决方案所做的)会导致类似的错误。

你使用什么编译器? - Gábor Bakos
Eclipse Luna Service Release 2 (4.4.2),目标兼容性1.8。 - Clashsoft
为什么你返回的是 copy 而不是被你修改后的那个映射表呢? - Makoto
你的意图不是要执行以下操作吗?copy.$plus$plus$eq(map); - Gábor Bakos
1
事实上,这解决了整个问题。正确的代码应该是 copy.$plus$plus$eq(map)。 :/ - Clashsoft
显示剩余3条评论
2个回答

4
如果使用原始类型Map,它可以工作,但您会失去类型安全性。不要使用原始类型,它们只存在于与Java 1.4及更早版本的兼容性中,这些版本没有泛型。
通配符无法正常工作,因为编译器不知道通配符?代表的确切类型是什么。原因与问题相同,即为什么您不能List<? extends T>上调用add()
请注意,通配符不意味着您可以使用任何类型的对象,其中键的类型扩展了K,值的类型扩展了V。相反,它意味着您有一个映射,其中键是某种特定但未知类型K,而值是某种特定但未知类型V。您无法在此类映射上调用$plus$plus$eq,因为编译器不知道确切的类型,因此无法检查它们。
原则上,您调用该方法的 Map 的通配符可能代表与您作为参数传递的 Map 的通配符不同的类型 - 即使在这种情况下您可以看到它们必须相同,因为您在此情况下使用相同的对象 map。您可以通过使用类型参数而不是通配符来解决此问题:
@Override
public <KK extends K, VV extends V> MutableMap<K, V> $plus$plus(Map<KK, VV> map)
{
    HashMap<K, V> copy = this.copy();
    map.$plus$plus$eq(map);
    return copy;
}

2
这是一个简化的例子,但它也无法编译。
class Foo<T> {

    void bar(Foo<? extends T> foo) {
        foo.bar(foo);
    }
}

假设foogoo的类型为Foo<? extends T>。这意味着foo的类型是Foo<U>,其中UT的子类型,而goo的类型是Foo<V>,其中V也是T的子类型。您不会期望foo.bar(goo)起作用,因为V可能不是U的子类型。因此,foo.bar(foo)也无法编译。这似乎很疯狂,因为foofoo是同一个实例,但参数是否适用仅取决于它们的编译时类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接