Java Map反序列化异常:单级Map反序列化为多级Map为何无即时报错?
问题现象
将一个Map<String, Object>对象序列化后,反序列化时强制转换为Map<String, Map<String, Object>>类型未抛出预期异常,调用restoredDoubleMap.get("KEY").keySet()时却触发ClassCastException。测试代码如下:
@Test public void test() throws IOException, ClassNotFoundException { // Map of objects Map<String, Object> singleMap = Map.of("KEY", "VALUE"); byte[] serializedMap; try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(singleMap); serializedMap = bos.toByteArray(); } // Map of maps of objects Map<String, Map<String, Object>> restoredDoubleMap; try (ByteArrayInputStream bis = new ByteArrayInputStream(serializedMap); ObjectInputStream ois = new ObjectInputStream(bis)) { // 预期会抛出异常但实际没有 restoredDoubleMap = (Map<String, Map<String, Object>>) ois.readObject(); } // 这行判断为true assertTrue(restoredDoubleMap instanceof Map<String, Map<String, Object>>); // 两个Map的equals判断通过 assertEquals(singleMap, restoredDoubleMap); System.out.println(singleMap); // 输出: {KEY=VALUE} System.out.println(restoredDoubleMap); // 输出: {KEY=VALUE} ??? // 这里触发ClassCastException System.out.println(restoredDoubleMap.get("KEY").keySet()); }
原因解析
泛型类型擦除导致运行时无泛型校验:Java泛型是编译期特性,运行时所有泛型参数都会被擦除,序列化过程中只会保存对象的原始类型(这里就是
Map),不会记录泛型的具体参数。因此反序列化时,JVM无法校验你强制转换的泛型类型是否匹配,所以(Map<String, Map<String, Object>>) ois.readObject()不会抛出异常。instanceof的泛型检查无效:assertTrue(restoredDoubleMap instanceof Map<String, Map<String, Object>>);这行代码在编译后,泛型信息会被擦除,实际执行的是restoredDoubleMap instanceof Map,而反序列化得到的确实是Map对象,所以判断必然为true,它并没有验证泛型参数的正确性。类型错误延迟到实际使用时暴露:当调用
restoredDoubleMap.get("KEY").keySet()时,get("KEY")返回的实际是String类型的"VALUE",但代码中期望它是Map<String, Object>。只有当尝试调用Map的keySet()方法时,JVM才会进行实际的类型检查,发现String无法转换为Map,从而抛出ClassCastException。equals方法不关心泛型类型:assertEquals(singleMap, restoredDoubleMap)能通过,是因为Map的equals方法只比较键值对的内容是否一致,和泛型参数无关。两个Map的键都是"KEY",值都是"VALUE",所以equals返回true。
内容的提问来源于stack exchange,提问作者sdorof

