【问题标题】:Type mismatch after extracting expression with generic return type提取具有泛型返回类型的表达式后类型不匹配
【发布时间】:2015-09-21 05:54:25
【问题描述】:

在执行提取表达式重构时,我偶然发现了 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&lt;Map.Entry&lt;? extends K, ? extends V&gt;&gt;。这一次,在声明的初始化器中指出了错误,说:

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

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

【问题讨论】:

  • 请注意Set&lt;? extends Map.Entry&lt;? extends K, ? extends V&gt;&gt; entrySet = mapToCopy.entrySet(); 会起作用。 JLS § 14.4.2 在增强的for 语句中讨论了Iterable 的翻译。另见this answer
  • @AndyBrown:请您详细说明“请注意,[...] 会起作用”。我必须修改什么才能使其正常工作?
  • 在java8中,也可以试试map.forEach( (key,value)-&gt;{ ... } )。键/值被推断为正确的类型,作为 K/V 的某些未知子类型。如果我们指定类型,API 也足够灵活 - map.forEach( (K key, V value)-&gt;{ ... } )

标签: java generics types refactoring capture


【解决方案1】:

让我们先回顾一下原始来源:

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&lt;? extends K, ? extends V&gt;相同的类型,因为通配符 (?) 总是代表未知 strong>,即不能保证entrySet 的入口键值中的通配符与entry 的键值(来自循环)相同。由于不能百分百确定这些类型是否兼容,编译器会引发编译时错误,**即使 ** 您可能确信这些类型在运行时将是相同的。

【讨论】:

  • 感谢您的回复。为什么您的第三个代码 sn-p 在循环语句中显示指示的错误?我无法重现。在我的第三个代码sn-p中,在声明语句中指出了错误。
  • 我认为,问题与for循环无关。我详细阐述了这个问题并开始了一个新线程here
猜你喜欢
  • 1970-01-01
  • 2016-10-29
  • 2021-05-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多