2015-07-03 108 views
5

执行提取表达式重构时,我偶然发现了Eclipse 4.4和Java 8生成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

请注意'Set <?扩展Map.Entry <?延伸K,?扩展V >> entrySet = mapToCopy.entrySet(); '会工作。 [JLS第14.4.2节](http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2)讨论了增强中“Iterable”的翻译为'声明。另请参阅[本答案](http://stackoverflow.com/a/16753901/5065475) –

+0

@AndyBrown:请您详细说明“请注意,[...]将工作”,请。我需要修改什么才能使其工作? – Marcus

+0

在java8中,还可以尝试'map.forEach((key,value) - > {...})'。键/值被推断为适当的类型,如K/V的某些未知子类型。如果我们规定类型 - 'map.forEach((K key,V value) - > {...}),那么API也是足够灵活的' – ZhongYu

回答

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的密钥值(来自循环)的通配符相同。由于不能百分之百确定类型是否兼容,编译器会产生编译时错误,即使**您可能有信心在运行系统中这些类型相同。

+0

感谢您的回应。为什么你的第三个代码片段在循环语句中显示指示的错误?我无法重现这一点。在我的第三个代码片段中,错误在声明语句中指出。 – Marcus

+0

我认为,问题与for循环无关。我详细阐述了这个问题,并开始了一个新的线程[here](http://stackoverflow.com/questions/32143844/type-mismatch-when-using-map-entryset)。 – Marcus