调用 clone() 时是否可以避免未经检查的转换?

Is it possible to avoid unchecked casting when calling `clone()`?

这是一个人为的例子来说明问题。我知道有 ways around this 不会生成编译器警告,您也可以禁用该警告。我想知道如果没有这些技巧,这是否可行。

鉴于此代码:

1 public static void main(String[] args) {
2    Map<String,String> map = null;
3
4    HashMap<String,String> hmap;
5
6    if(map instanceof HashMap)
7        hmap = (HashMap<String,String>)map;
8    else
9        hmap = new HashMap<String,String>(map);
10
11   map = (Map<String,String>)hmap.clone();
12
13    Object o = hmap.clone();
14    if(o instanceof Map<?,?>)
15        map = (Map<String,String>)o;
16 }

第11行15行的代码生成编译器警告:

Unchecked cast from Object to Map<String,String>

第 11 行有点好理解:Object.clone() returns 和 Object,并且在转换之前没有进行 instanceof 检查。程序员 知道 克隆将是 Map<String,String>,但编译器无法证明它。

不过,第 15 行让我感到困惑。通常,使用 instanceof 检查变量的类型然后立即转换它不会产生这样的警告。实际上,像这样用非参数化 classes 替换代码将不会在以下代码行中的任何一行上产生警告:

static class A {}
static class B extends A implements Cloneable {
    public Object clone() { return null; }
}
public static void main(String[] args) {
    A a = null;

    B b;

    if(a instanceof B)
        b = (B)a;
    else
        b = new B();

    a = (A)b.clone();

    Object o = b.clone();
    if(o instanceof A)
        a = (A)o;
}

回到原始代码(使用 Map<String,String> 引用),即使在代码末尾添加这个笨拙的结构也会产生类似的警告:

map = (Map<String,String>)hmap.getClass().cast(o);

这次的警告是Unchecked cast from capture#11-of ? extends HashMap to Map<String,String>。尝试写入:

map = HashMap<String,String>.class.cast(o);

生成编译器 错误 因为它无法弄清楚 HashMap<String,String>.class 是一个静态 class 引用,其方式与例如HashMap.class,所以我们要用"correct"类型的引用来调用Class.cast.

这是Java做不到的吗?

Is this something that Java just can't do?

是的,设计就是这样。

查看 HashMap 克隆方法的 java 来源 (1.8):

@SuppressWarnings("unchecked")
@Override
public Object clone() {
    HashMap<K,V> result;
    try {
        result = (HashMap<K,V>)super.clone();
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
    result.reinitialize();
    result.putMapEntries(this, false);
    return result;
}

出于相同的目的,它使用 @SuppressWarnings("unchecked") 来抑制 super.clone() 上的警告。

你不能完全避免它,但如果你的代码有很多 clone() 方法,你可以通过提取方法来最小化这些警告:

@SuppressWarnings("unchecked")
HashMap<String,String> cloneWithoutWarning(HashMap<String,String> map) { return (HashMap<String,String>) map.clone(); } 

Usually, checking the type of a variable using instanceof and then immediately casting it will not generate such a warning.

这不是真的。 instanceofs 对施放警告没有影响。

这不是关于此演员可能 ClassCastException 的警告。未经检查的转换意味着 Java 无法安全地进行转换。这意味着转换可能在没有 ClassCastException 的情况下通过,但类型不匹配。这可能会导致 ClassCastException 来自意想不到的地方。

在这种情况下这是一个真正的问题。 HashMap.clone() returns Object 为了向后兼容。没有办法判断它的实现是否是类型安全的。例如:

import java.util.*;
class Main {
    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap() {
            @Override
            public Object clone() {
                HashMap o = (HashMap)super.clone();
                o.put("x", new Object());
                return o;
            }
        };
        Map<String,String> copy = (Map<String,String>)map.clone(); // will pass
        System.out.println(copy.get("x")); // no cast but fails with ClassCastException
    }
}

clone() 有问题。如果可能,只需创建一个新的 HashMap.

如果你必须使用克隆,只使用安全转换:

Map<?, ?> copy = (Map<?, ?>)map.clone();
System.out.println((String)copy.get("x")); // explicit cast will fail

除非您正在处理遗留代码(非通用),否则应考虑未经检查的强制转换 hacks/workarounds。