14

给出以下代码:

public static void main(String[] args) {
        HashMap<String, String> hashMap = new HashMap<>();
        HashMap<String, Object> dataMap = new HashMap<>();
        dataMap.put("longvalue", 5L);

        class TestMethodHolder {
            <T> T getValue(Map<String, Object> dataMap, String value) {
                return (T)dataMap.get(value);
            }
        }

        hashMap.put("test", new TestMethodHolder().<String>getValue(dataMap, "longvalue"));
        String value = hashMap.get("test"); // ClassCastException occurs HERE
        System.out.println(value);
    }

这段代码可以编译,这对我来说并不奇怪,而是 ClassCastException 发生在 get 行而不是它上面的 put 行,尽管我对可能发生的事情有一个有根据的猜测。由于泛型类型在运行时被擦除,因此 getValue() 中的强制转换实际上永远不会在运行时发生,并且实际上是对 Object 的强制转换。如果该方法将按如下方式实现,则运行时强制转换将发生并且它将在 put 行上失败(如预期的那样)。谁能证实这一点?

class TestMethodHolder {
        String getValue(Map<String, Object> dataMap, String value) {
            return (String)dataMap.get(value);
        }
    }

这是使用泛型的已知缺陷还是奇怪?那么在调用方法时使用 <> 表示法是不好的做法吗?

编辑:我使用的是默认的 Oracle JDK 1.7_03。

上面的另一个隐含问题:原始 getValue 中的转换是否仍然在运行时发生,但转换实际上是对 Object - 或者编译器是否足够聪明,可以完全避免在运行时不发生这种转换?这可能解释了人们在运行 ClassCastException 时注意到的发生位置的差异。

4

3 回答 3

12

线

return (T)dataMap.get(value);

生成一个未经检查的强制转换警告,并且根据规范,任何此类警告的存在都会使您的代码类型不安全。第ClassCastException一次尝试将类型不安全的结果分配给错误类型的变量时会发生这种情况,因为这是编译后的代码第一次进行类型检查。

请注意,Eclipse 的编译器插入的类型检查比 JLS 要求的要多,因此,如果您在 Eclipse 中编译,hashMap.put调用将失败并显示CCE. 编译器知道此调用必须有两个String参数,因此可以在实际方法调用之前插入类型检查。

正如您所猜测的那样,如果您将泛型替换T为特定的String,则类型检查会在该点发生 - 并且失败。

于 2013-05-17T14:19:03.797 回答
2

编译器依赖于类型安全来做出假设并进行转换/优化。不幸的是,类型安全可以通过未经检查的强制转换来破坏。如果你的程序包含不正确的未经检查的强制转换,那么编译器应该做什么就不清楚了。理想情况下,它应该在未检查强制转换的确切点进行运行时检查,在您的示例中,当Object被强制转换为T. 但这是不可能的,因为擦除不完全是类型系统的一部分。

在您的示例中的其他任何地方,类型都是正确的,因此编译器可以假设getValue()真的返回 a String,没有必要仔细检查。但是进行检查也是合法的,就像 Eclipse 编译器所做的那样(可能是因为它将返回值分配给String本地临时变量)。

所以坏消息是,如果你的程序包含不正确的未检查强制转换,它的行为是未定义的......所以通过严格的推理确保你所有的未检查强制转换都是正确的。

一个好的做法是检查所有未经检查的强制转换,以便您可以合法地抑制未经检查的警告。例如

        <T> T getValue(Map<String, Object> dataMap, String value, Class<T> type) 
        { 
            Object value = dataMap.get(value);
            if(value!=null && !type.isInstance(value))  // check!
                throw new ClassCastException();

            @SuppressWarning("unchecked")
            T t = (T)value;  // this is safe, because we've just checked
            return t;
        }

请参阅我对类似问题的回答:Lazy class cast in Java?

于 2013-05-17T15:33:47.947 回答
0

此类型信息在编译期间被删除(参见Neal Gafter 的“Reified Generics for Java”)。

实际上,您可以使用Collections实用程序方法保护您的集合:

Class<String> type = String.class;
Map<String, String> hashMap = new HashMap<>();
Map<String, String> map = Collections.checkedMap(hashMap, type, type);

Map rawType = map; // pre-Java 1.5 code knows nothing about generics
rawType.put(1, 2); // throws ClassCastException at runtime
于 2013-05-17T15:53:28.130 回答