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

时间:2017-10-20 02:32:33

标签: 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

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

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

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

file = open("createdFile.txt", "w")
file.write("User Data Number:\n")
file.write("10101")
file.close()
file = open("createdFile.txt", "r")
getUserNumber = file.readline(2)
print(getUserNumber)
file.close()

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

我问的原因是我在生产代码中看到了这个(更多涉及的)版本,我想知道,即使这可能是令人困惑和臭,也可以保证它在功能方面起作用。任何意见都将不胜感激。

1 个答案:

答案 0 :(得分:2)

这种编码风险很大!!虽然它会编译,但你会注意到编译器抱怨警告:

  

注意:NoRulesForMe.java使用未经检查或不安全的操作。
  注意:使用-Xlint重新编译:取消选中以获取详细信息。

这些警告,特别是因为您使用泛型,不应该被忽略,也不应该被抑制。您必须绝对确定(逻辑上遵循代码)演员阵容是安全的,并且以后不会引起一些问题。最好始终以这样的方式编写代码,以便在编译器时间而不是运行时发现和拾取错误。这里编译器发出的警告告诉你事情可能会出错。

您正在将myMap作为Object传递给方法castWildly,当您投射时,您正在从Object投射到Map

编译器可以推断出代码中的T的类型目标为Map<String, Double>,因此可以推断出这一点。但是,在投射时,它没有关于Object value(或Object 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;
    }
}
运行上述代码时

运行时错误:

  星星   3.14
  线程“main”中的异常java.lang.ClassCastException:java.lang.String无法强制转换为java.lang.Double     在NoRulesForMe.main(NoRulesForMe.java:19)

有关详细信息,请阅读Joshua Bloch撰写的Effective Java;第24项:消除未经检查的警告。 (此项目位于Generics标题下)。