【问题标题】:Why am I losing type information?为什么我会丢失类型信息?
【发布时间】:2015-06-20 09:44:12
【问题描述】:

我发现 Maps、rawtypes 和泛型会发生一些有趣的事情。以下代码:

static {
          Map map = new HashMap ();
          Set <Map.Entry> set = map.entrySet ();
          for (Map.Entry entry : set) {} // fine 
          for (Map.Entry entry : map.entrySet()) {} // compilation error
}

我收到关于类型不兼容的编译错误,即:“无法将对象强制转换为条目”。

Ideone for convenience

如果没有变量再次存储,为什么entrySet() 上的迭代器会丢失类型信息?

rawtypes 不应该影响类型,所以Map.Entry 突然变成了一个对象。还是我弄错了?

【问题讨论】:

  • Set &lt;Map.Entry&gt; set = map.entrySet (); 是允许的“未经检查的分配”。这就是允许第一个迭代器工作的原因。您可以通过在第二种情况下明确强制转换来获得相同的结果:for(Map.Entry entry : (Set&lt;Map.Entry&gt;)map.entrySet()) { }
  • 要遵循的最基本规则是:永远不要使用原始类型。它们很容易避免(通常用通配符参数替换,即Set 变成Set&lt;?&gt;),它们存在的唯一原因是向后兼容预泛型代码。
  • @JoachimSauer,同意,但我认为这主要是一个学术问题。编辑:好的,所以它不是学术的,但无论如何这是一个有趣的问题。
  • @Joachim 对。这在维护前泛型时代的遗留代码时无济于事,而且无论如何对这个问题都没有真正的建设性......

标签: java iterator type-erasure generics


【解决方案1】:

您的示例使您看起来拥有从未有过的类型信息。你写过:

Map map = new HashMap ();
Set <Map.Entry> set = map.entrySet();
for (Map.Entry entry : set) {} // fine 
for (Map.Entry entry : map.entrySet()) {} // compilation error

但是map.entrySet() 返回的是Set,而不是Set &lt;Map.Entry&gt;。您执行了一个“添加”类型信息的未经检查的分配。

在第二个 for 循环中,我们不知道 Set 中的内容,因此我们无法在没有显式转换的情况下迭代 Set &lt;Map.Entry&gt;

例如,将原始示例与我们未通过未检查分配“添加”类型信息的示例进行比较。

Map map = new HashMap();
Set set = map.entrySet();
for (Map.Entry entry : set) {
} // Object cannot be cast to Entry
for (Map.Entry entry : map.entrySet()) {
} // Object cannot be cast to Entry

在这种情况下,两个 for 循环都会产生编译错误。

Java 语言规范第 4.8 节中记录了此行为:

构造函数的类型(第 8.8 节)、实例方法(第 8.8 节、第 9.4 节)或 非继承自原始类型 C 的非静态字段(第 8.3 节)M 它的超类或超接口是在 对应于 C 的泛型声明。 原始类型 C 与其在泛型声明中的类型相同 对应于C。

【讨论】:

  • "但是 map.entrySet() 正在返回 Set,而不是 Set 。" -> 这似乎与文档相矛盾,文档明确指出entrySet() 的返回类型是Set&lt;Entry&gt;
  • @Unihedro:区别在于map 有一个原始类型,这意味着当您在其上调用方法时,所有通用信息都将被忽略。所以map.entrySet() 确实返回Set 而不是Set&lt;Map.Entry&gt;
  • @Unihedro,如果您在声明中声明了泛型类型,则只能引用Map.Entry。通过使用 rawtypes 你已经改变了方法契约。泛型类的 Javadoc 没有提到如果没有给出类型信息,它们会返回原始类型(例如 List.subList,但这就是它们的设计方式。
  • 这在4.6 中也有提及:“如果方法或构造函数的签名被擦除,方法的返回类型和泛型方法或构造函数的类型参数也会被擦除。 ".
【解决方案2】:

我认为简短的回答是 Java 在某些情况下允许“未经检查的强制转换”,但在其他情况下不允许。处理原始类型(没有指定类型的通用类型)就是这些实例之一。

请记住,for (Map.Entry entry : set) 相当于:

Iterator it = set.iterator();
while (it.hasNext())
{
    Map.Entry entry = it.next();
}

任务:

Set set = map.entrySet();

被允许并且不会产生任何警告,因为您没有引入任何新类型,但在 for 循环中 it.next() 将返回类型 Object 如果您在没有明确指定的情况下分配它,您将获得编译器异常演员表。

任务:

Set <Map.Entry> set = map.entrySet(); 

是允许的,但由于显式类型 Map.Entry 而会生成“未经检查的强制转换”警告,并且在 for 循环中,it.next() 将返回类型 Map.Entry,并且分配将正常工作。

您可以像这样将显式转换放在 for 循环中:

for(Map.Entry entry : (Set<Map.Entry>) map.entrySet())

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-09
    • 1970-01-01
    • 2010-11-13
    • 2023-03-14
    • 2021-01-18
    • 2019-07-22
    • 2014-06-14
    相关资源
    最近更新 更多