使用泛型返回类型提取表达式后键入不匹配

时间:2015-07-03 11:39:19

标签: java generics types refactoring capture

在执行提取表达式重构时,我偶然发现了Eclipse 4.4和Java 8 build 45中的一些奇怪的行为。以下示例显示了应用提取重构之前的原始和无错误代码:

import java.util.Map;
import java.util.Set;

public class MyMap<K, V> {
    public void putAll(final Map<? extends K, ? extends V> mapToCopy) {
        for (Map.Entry<? extends K, ? extends V> entry : mapToCopy.entrySet()) {
        }
    }
}

Eclipse重构的结果如下所示,并导致下面的错误消息引用了循环声明中entrySet的读访问权:

    public void putAll(final Map<? extends K, ? extends V> mapToCopy) {
        Set<?> entrySet = mapToCopy.entrySet();
        for (Map.Entry<? extends K, ? extends V> entry : entrySet) {
                                                         ^^^^^^^^
        }
    }

Type mismatch: cannot convert 
    from element type capture#3-of ? 
    to Map.Entry<? extends K,? extends V>

我将entrySet声明的类型更改为Set<Map.Entry<? extends K, ? extends V>>。这一次,错误在声明的初始化程序中指出,说:

    public void putAll(final Map<? extends K, ? extends V> mapToCopy) {
        Set<Map.Entry<? extends K, ? extends V>> entrySet = mapToCopy.entrySet(); 
                                                            ^^^^^^^^^^^^^^^^^^^^
        for (Map.Entry<? extends K, ? extends V> entry : entrySet) {
        }
    }

Type mismatch: cannot convert 
    from Set<Map.Entry<capture#1-of ? extends K,capture#2-of ? extends V>> 
    to Set<Map.Entry<? extends K,? extends V>>

由于原始代码编译,我有点困惑。也许有人可以帮助我并给出解释?提前谢谢!

1 个答案:

答案 0 :(得分:0)

让我们先回顾原始资料来源:

public void putAll(final Map<? extends K, ? extends V> mapToCopy) {
    for (Map.Entry<? extends K, ? extends V> entry : mapToCopy.entrySet()) {
    }
}

在内部(和运行时),这将被编译并将作为:

public void putAll(final Map mapToCopy) {
    for (Iterator<Map.Entry> iterator = mapToCopy.iterator; iterator.hasNext();) {
    }
}

其中? extends K? extends V将替换为类型擦除后的一些实际类型。编译器将知道这些类型是什么,并且不会因类型不兼容而引发Exception

另一方面,如果您将源重构为此,

public void putAll(final Map<? extends K, ? extends V> mapToCopy) {
    Set<Entry<? extends K, ? extends V>> entrySet = mapToCopy.entrySet();
                                                    ^^^^^^^^
    for (Map.Entry<? extends K, ? extends V> entry : entrySet) {

    }
}

然后编译器将没有证据表明entrySet Map.Entry<? extends K, ? extends V>保持类型相同,只是因为通配符(?)始终代表某些东西未知,即无法保证entrySet条目键值中的通配符与entry&#39;相同。 s键值(来自循环)。不是百分之百确定类型是兼容的,编译器会引发编译时错误,**即使**你可能有信心这些类型在运行时是相同的。