在初始化Map中放置不一致类型的对象 - 预期和合法?

flo*_*w2k 5 java generics collections casting

今天,我发现即使对象无法转换为正确的类型,也能够put在现有Map的对象中生成对象.

首先,让我从一个简单的例子开始:

Map<Integer, String> myMap = new HashMap<>(); //plain old hashmap
myMap.put(9,"star"); //no problem

myMap.put(10, 1.2); //Incompatible type, the compiler yells
Map<Integer, Double> aMap = (Map<Integer, Double>) myMap; //Cannot cast, the compiler yells
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都是预期的,因为你应该将一个不一致类型的对象放入已经构造的Map中.现在让我们考虑一下:

public class NoRulesForMe {

    static Object theRing;

    public static void main(String[] args){

        Map<Integer, String> myMap = new HashMap<>();
        myMap.put(9,"star");

        Map<Integer, Double> myMapMorphed = castWildly(myMap);
        myMapMorphed.put(99, 3.14);

        System.out.println(myMapMorphed.get(9)); //"star", as we put in
        System.out.println(myMapMorphed.get(99)); //3.14, as we put in
    }

    public static <T> T castWildly(Object value){
        theRing = value;
        T morphed = (T) theRing;
        return morphed;
    }
}
Run Code Online (Sandbox Code Playgroud)

我很惊讶这不会导致运行时错误 - Map如何实现这一点,这种行为是在JLS或API中指定的,因此可以依赖吗?

我问的原因是我在生产代码中看到了这个(更多涉及的)版本,我想知道,即使这可能是令人困惑和臭,也可以保证它在功能上有所作为.任何输入将不胜感激.

小智 2

这种类型的编码是非常危险的!尽管它可以编译,但您会注意到编译器会发出警告:

注意:NoRulesForMe.java 使用未经检查或不安全的操作。
注意:使用 -Xlint:unchecked 重新编译以了解详细信息。

这些警告,特别是当您使用泛型时,永远不应该被忽略或仅仅抑制。您必须绝对确定(逻辑上遵循代码)强制转换是安全的并且不会在以后引起一些问题。最好始终以这样的方式进行编码:在编译时而不是在运行时发现和拾取错误。编译器在这里给出的警告是告诉你事情可能会出错。

myMap您将as an传递Object给该方法castWildly,当您进行转换时,您正在将 from 转换Object为 a Map

编译器可以推断出T您的代码中的目标类型为Map<String, Double>,因此可以推断出这一点。但是,在转换时,它没有关于 (Object valueObject theRing) 是什么(子)类型的信息。因此它无法检查强制转换是否安全(特别是类型安全)。

当您从地图中检索值时,此代码就会出现问题。下面的代码额外添加了一行,并且代码可以编译(带有与上面相同的警告)。Double这是因为,当从映射中检索一个值时,该Map<String, Double>值在编译器进行类型检查时被声明为绝对有效......但是,在运行时,您的代码将崩溃(运行时崩溃错误如下所示)。这是非常危险的编码方式,尤其是在生产代码中。您宁愿让编译器给您带来错误,也不愿部署生产代码来编译并让您的产品在运行时崩溃。

public class NoRulesForMe {

    static Object theRing;

    public static void main(String[] args){

        Map<Integer, String> myMap = new HashMap<>();
        myMap.put(9,"star");

        Map<Integer, Double> myMapMorphed = castWildly(myMap);
        myMapMorphed.put(99, 3.14);

        System.out.println(myMapMorphed.get(9)); //"star", as we put in
        System.out.println(myMapMorphed.get(99)); //3.14, as we put in

        // added to show why this style of coding causes problems
        Double testValue1 = myMapMorphed.get(9);
    }

    public static <T> T castWildly(Object value){
        theRing = value;
        T morphed = (T) theRing;
        return morphed;
    }
}
Run Code Online (Sandbox Code Playgroud)

运行上述代码时出现运行时错误:

star
3.14
线程“main”中出现异常 java.lang.ClassCastException:java.lang.String 无法在 NoRulesForMe.main 处转换为 java.lang.Double(NoRulesForMe.java:19)

如需了解更多信息,请阅读 Joshua Bloch 所著的《Effective Java》;第 24 项:消除未经检查的警告。(该项目位于“泛型”标题下)。