泛型 Oddity - 我可以将 Long 值插入到 Map<String、String>,它可以编译,并且在运行时不会失败

2022-09-04 03:14:36

给出以下代码:

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行上,而不是它上面的放置行,尽管我确实对可能发生的事情有一个有根据的猜测。由于泛型类型在运行时被擦除,因此 getValue() 中的强制转换实际上从未在运行时发生,并且实际上是对 Object 的强制转换。如果该方法将按如下方式实现,则将发生运行时强制转换,并且它将在放置行上失败(如预期的那样)。任何人都可以证实这一点吗?

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

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

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

上面的另一个隐含问题:原始getValue中的强制转换是否仍在运行时发生,但强制转换实际上是对象 - 或者编译器是否足够聪明,可以消除这种转换,使其在运行时根本不发生?这可以解释人们在运行ClassCastException时注意到的发生位置的差异。


答案 1

线

return (T)dataMap.get(value);

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

请注意,Eclipse 的编译器插入的类型检查比 JLS 强制的要多,因此,如果在 Eclipse 中编译,调用将失败,出现 。编译器知道此调用必须具有两个参数,因此可以在实际方法调用之前插入类型检查。hashMap.putCCEString

正如您所猜测的那样,如果将泛型替换为 specific ,则类型检查将在此时发生,并且会失败。TString


答案 2

编译器依靠类型安全来做出假设并进行转换/优化。不幸的是,类型安全可以通过未经检查的投射来破坏。如果您的程序包含不正确的未经检查的强制转换,则不清楚编译器应执行的操作。理想情况下,它应该在未选中的强制转换的确切位置进行运行时检查,在您的示例中,当转换为 。但这是不可能的,因为擦除并不是类型系统的一部分。ObjectT

在示例中的其他任何地方,类型都是健全的,因此编译器可以假设确实返回一个,因此无需仔细检查。但是像Eclipse编译器那样进行检查也是合法的(可能是因为它将返回值分配给局部临时变量)。getValue()StringString

所以坏消息是,如果你的程序包含不正确的未经检查的投射,它的行为是不确定的。因此,通过严格的推理,确保所有未经检查的演员表都是正确的。

一个好的做法是检查所有未选中的强制转换,以便您可以合法地禁止显示未选中的警告。例如

        <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;
        }

看到我对类似问题的回答:Java中的懒惰类?


推荐