2017-10-20 35 views
5

今天,我发现一个能够put的对象在一个现有的Map中即使对象不能被转换为正确的类型。在初始化的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; 
    } 
} 

我很惊讶,这并没有导致运行时错误 - 如何地图实现这一目标,并且是在JLS或API指定了此行为,因此可以被信赖的?

我想问的原因是我在生产代码中看到了一个(更多参与的)版本,并且我怀疑,即使这可能是混乱和臭味,它是否可以保证以功能方式工作。任何输入将不胜感激。

+0

你需要谷歌“键入擦除”。 –

+0

这在Java的泛型实现中是_inherent_。 –

+0

@LouisWasserman您可否详细说明* Java的泛型中固有的*是什么?编译器没有抱怨,我并不感到惊讶。但是我认为更大的问题是Java如何允许“Double”可以存储为“String”。 – flow2k

回答

1

这种类型的编码风险很大!虽然它会编译,但您会注意到编译器会发出警告:

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

这些警告,特别是因为您使用的是泛型,不应该被忽略,也不应该被压制。您必须确保(逻辑上遵循代码)演员安全,并且不会在稍后导致某些问题。最好始终以这样的方式进行编码,即在编译器时间而不是运行时间发现并拾取错误。编译器给出的警告告诉你事情可能会出错。

你是通过你的myMapObject的方法castWildly,当你是铸造你是从Object铸造到Map

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

当您从地图中检索值时,此代码的问题就出现了。下面的代码有一个额外的添加行,并且编译代码(与上面相同的警告)。这是因为,在编译器在运行时执行类型检查时,声明为Map<String, Double>的映射中检索出的值为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
异常在线程 “主要” java.lang.ClassCastException:java.lang.String中不能在NoRulesForMe.main被强制转换为java.lang.Double中 (NoRulesForMe.java:19)

更多信息,阅读由Joshua Bloch撰写的Effective Java;项目24:消除未检查的警告。 (这个项目是在泛型标题下)。