【问题标题】:Java raw reference to parameterized type doesn't respect parameterJava对参数化类型的原始引用不尊重参数
【发布时间】:2020-11-01 14:01:47
【问题描述】:

我正在尝试各种实例化 HashMap 的方法,但我遇到了一个问题。 在创建从 String 到 String 的映射时,可以在以下选项中进行选择:

Map<String, String> map = new HashMap<String, String>();
Map<String, String> map = new HashMap<>();
Map<String, String> map = new HashMap();

我认为使用第一个的好处是将内存中的整个对象绑定到键和值的字符串类型,因为引用(映射)将指向 HashMap

运行以下代码不会引发错误并显示合理的输出:

Map<String, String> map = new HashMap<String, String>();
map.put("hi", "there");
Map map2 = map;
map2.put(5, 4);
System.out.println(map.get("hi"));
System.out.println(map2.get("hi"));
System.out.println(map2.get(5));
System.out.println(map2.get(5).getClass());
System.out.println(map.size());

输出是:

there
there
4
class java.lang.Integer
2

我们可以清楚地看到,两个引用都指向内存中的同一个对象,我认为它的类型应该是 HashMap,但是当引用没有参数化时,我们可以添加 Object 的任何子类型。

我的问题是,既然参数化的构造函数对于存储在内存中的对象来说并不重要,我们为什么要重复右侧的类型呢? 为什么要写这个:

Map<String, String> map = new HashMap<String, String>();

代替这个:

Map<String, String> map = new HashMap();

即使效果一样?

【问题讨论】:

  • “我认为应该是 HashMap&lt;String, String&gt; 类型的” - 没有这样的东西。在执行时,只有一个HashMap。至于你为什么不使用原始类型:因为这意味着你甚至没有获得编译时类型安全。 (我希望您届时会收到警告。)
  • 这种编写初始化的方式:Map&lt;String, String&gt; map = new HashMap&lt;String, String&gt;(); 被缩写为Map&lt;String, String&gt; map = new HashMap&lt;&gt;();,因为键、值类型已经在等号的左侧声明。这意味着以一种或另一种方式编写是相同的。今天的 IDE 会将其标记为不必要。
  • @JonSkeet 当写 Map map = new HashMap();我仍然得到编译时安全,我在问为什么我们也应该在右侧添加类型
  • @Victor:不,你没有。您正在将“某种映射”(原始 HashMap 类型)分配给类型化变量。这就是为什么你会收到警告。 (“注意:Test.java 使用未经检查或不安全的操作。”)我强烈建议改用“菱形运算符”形式 (new HashMap&lt;&gt;()),并养成在任何地方都避免使用原始类型的习惯。
  • @JonSkeet 我明白了,谢谢!

标签: java oop collections polymorphism


【解决方案1】:

由于类型擦除,生成的代码没有区别。泛型所要做的就是获得编译器关于代码正确性的反馈。只有当您的代码没有“未检查”和“原始类型”警告时,您才能确定不会发生“堆污染”,即存储的对象类型不正确。

在这方面,行

Map<String, String> map = new HashMap();

是有问题的,因为它会生成编译器警告。当然,当你看到它并对他的含义有足够的了解时,你就会知道它不会造成任何伤害。

相比之下,类似的行

Map<String, String> map = new HashMap(oldMap);

是否允许使用不正确的内容初始化地图。当然,您现在可以检查oldMap 的类型,看看它是否兼容,并再次确信这种特定情况是可以的。但是这种方法违背了泛型的全部目的,让编译器为您检查这一点,您可以通过插入 &lt;&gt; 轻松实现。

一般来说,当你有一个大的代码库时,你希望它完全没有警告,所以你不必去每个位置,看看它是否可以被认为是无害的,即使它只需要看一眼.


插入&lt;&gt; 的必要性与历史有关。当 Java 5 发布时,所有方法调用的默认值都是执行泛型检查,因此说“兼容性问题”是使 原始类型 成为构造函数调用的默认值的原因并不令人信服。我的猜测是,javac 的开发者在类型推断的实现上遇到了困难,并且有发布时间的压力。

请注意,对于 Java 5 和 Java 6,开发人员甚至必须为每个泛型实例显式插入完整类型。

在 Java 7 中,通过引入“菱形运算符”&lt;&gt; 让编译器推断出泛型类型,这一问题得到了缓解,正如它本来应该做的那样。但是,为了“兼容性”,仍然需要明确地请求推理,尽管如前所述,对于方法调用,没有选择加入机制,并且从泛型的第一天开始就可以使用。对我来说,原始类型默认是一个设计错误。

但是,出于实际目的,如上所述,您希望应用程序没有警告,并由编译器执行检查,因此,您应该始终插入必要的 &lt;&gt;

从 Java 10 开始,您可以声明局部变量,例如

var map = new HashMap<String, String>();

消除声明中的所有冗余。

【讨论】:

  • 美丽的解释与历史和实际意义有关。非常感谢!
猜你喜欢
  • 2013-06-01
  • 2012-01-18
  • 1970-01-01
  • 2021-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多