为什么Java的clone()方法需要非受检强制类型转换?
问题场景
现有如下代码片段:
public static void main(String[] args) { HashMap<Integer, String> mif = new HashMap<>(); mif.put(2, "haha"); mif.put(3, "hello"); mif.put(1, "hi"); HashMap<Integer, String> mifClone = mif.clone(); // 编译报错
最后一行无法通过编译,报错信息如下:
Type mismatch: cannot convert from Object to HashMap<Integer,String>
将该行修改为强转写法后可以通过编译,但会触发类型安全警告:
HashMap<Integer, String> mifClone = (HashMap<Integer, String>)mif.clone(); // 类型安全警告
警告信息如下:
Type safety: Unchecked cast from Object to HashMap<Integer,String> Java(16777761)
待解答问题
- 为什么编译器已经明确知道
mif的泛型类型是HashMap<Integer, String>,调用clone()的返回值依然是Object,而不是直接返回对应泛型类型的实例? - 不使用警告压制注解的前提下,怎么修复这个非受检类型转换警告?
问题解答
关于第一个问题
核心原因是两点:历史兼容包袱 + Java泛型的擦除实现机制,和编译器是否识别变量泛型类型无关:
HashMap的clone()方法诞生于JDK 1.2,远早于JDK 5引入泛型的时间点。为了保证二进制向后兼容性,这个方法的签名在泛型上线后没有做本质修改,直到最新的JDK版本,HashMap#clone()的显式返回值依然是Object,没有协变为具体的泛型HashMap类型。- 就算后续JDK把
clone()的返回值协变为原生HashMap(无泛型参数的raw type),强转成HashMap<Integer, String>依然会触发警告——Java泛型是编译期擦除实现的,运行时JVM根本不存在HashMap<Integer,String>这个类型,只有原生HashMap,编译器无法在运行时校验Map里的键值是不是真的是Integer和String类型,这类转换天然就是非受检的。 - Java本身不存在“根据调用变量的泛型类型自动推导方法返回值”的机制,方法的返回值类型在类定义时就已经固定,编译器不会因为调用者的泛型声明自动修改方法的返回值类型。
关于第二个问题
工程上最推荐、最简洁的无警告实现方式,是放弃使用clone()方法,改用HashMap自带的Map参数构造器复制实例:
HashMap<Integer, String> mifClone = new HashMap<>(mif);
这个构造方法的签名是public HashMap(Map<? extends K, ? extends V> m),本身对泛型参数做了完整的类型约束,编译期就能完成全部类型检查,不会产生任何警告,实现的效果和HashMap的浅克隆完全一致:会把原Map里的所有键值对引用复制到新Map中,不会额外产生深层拷贝的开销。
如果一定要使用clone()的逻辑(极不推荐,Java集合框架的clone方法本身就是业界公认的设计缺陷),可以先把clone结果强转为通配符泛型的HashMap<?, ?>——这个转换是运行时可校验的,不会产生警告,再通过构造方法传入生成具体泛型的实例:
HashMap<?, ?> tempClone = (HashMap<?, ?>) mif.clone(); HashMap<Integer, String> mifClone = new HashMap<>(tempClone);
这种写法没有任何非受检转换,也不需要加警告压制,但写法冗余,没有实际收益,完全不如直接用构造方法复制的方案。
内容的提问来源于stack exchange,提问作者Troskyvs
相关产品推荐
相关产品推荐

