首页 > 解决方案 > Java对参数化类型的原始引用不尊重参数

问题描述

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

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

我认为使用第一个的好处是将内存中的整个对象绑定到键和值的 String 类型,因为 reference(map) 将指向 HashMap<String, String> 类型的实际对象

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

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<String, String>,但是当引用未参数化时,我们可以添加 Object 的任何子类型。

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

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

对此:

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

即使它具有相同的效果?

标签: javaoopcollectionspolymorphism

解决方案


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

对此,行

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

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

相比之下,类似的线

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

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

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


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

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

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

但是,出于实际目的,如上所述,您希望使用编译器执行检查的无警告应用程序,因此,您应该始终插入必要的<>.

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

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

消除声明中的所有冗余。


推荐阅读