泛型奇怪 - 我可以在Map <String,String>中插入一个Long值,它会在运行时编译并且不会失败

Gre*_*nie 14 java generics

提供以下代码:

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);
    }
Run Code Online (Sandbox Code Playgroud)

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

class TestMethodHolder {
        String getValue(Map<String, Object> dataMap, String value) {
            return (String)dataMap.get(value);
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

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

上面提到的另一个隐含问题:原始getValue STILL中的强制转换是在运行时发生的,但是强制转换实际上是对象 - 或者编译器是否足够聪明,以便在运行时不会发生此强制转换?这可能解释了人们在运行时注意到ClassCastException发生的位置的差异.

Mar*_*nik 12

线

return (T)dataMap.get(value);
Run Code Online (Sandbox Code Playgroud)

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

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

正如您猜测的那样,如果您T使用特定的替换泛型String,那么类型检查将在该点发生 - 并且失败.


Zho*_*gYu 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;
        }
Run Code Online (Sandbox Code Playgroud)

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