文章目录

  • ​​一、前情提要​​
  • ​​二、寻找问题​​
  • ​​三、验证问题​​



一、前情提要

前几天测试给我提了一个缺陷,提示类型转化报错,可是我怎么看对应的代码都想不明白问题在哪里



1、缺陷背景

将模型大致抽象一下,就是其他同事将前台的数据放入到一个map中,然后再把这个map转为json字符串传给后台,紧接着再把这个json字符串反序列化为一个map对象,最后把这个map对象传递给我。



2、报错提示如下:

光看报错提示,其实还是很开心的,这明显是一个类型转化异常,那必然是我将一个Integer类型的数字转化为一个String类型的字符串导致的,继续往下看



看我在Map<String, String>集合中,存入Integer类型数据_反序列化


3、代码报错的位置

定位到报错的位置是下面的方法,然后我陷入了沉思,String转Integer?你这是哪门子的类型转化异常?我这不是两个String?



看我在Map<String, String>集合中,存入Integer类型数据_java_02


4、该方法只是一个简单的判空方法



看我在Map<String, String>集合中,存入Integer类型数据_java_03




二、寻找问题

1、捞日志看接收参数

心里窃喜,还好我把接收的参数打印在了日志里面。结果发现我在打日志的时候是直接传的map,并没有对其进行自定义的序列化,以至于我无法看到数据对应的类型(后文会说明,这个数据类型很重要),下次自己得记住要序列化



看我在Map<String, String>集合中,存入Integer类型数据_hashmap_04


2、观察前台传给后台的参数

无奈,我只能前台看对应的参数情况,最终在前台的参数里面找到了它。发现一个参数不像是字符串



看我在Map<String, String>集合中,存入Integer类型数据_反射_05


3、再次执行判断方法

然后我用数字类型的参数去执行StringUtils.isNotBlank()方法,只能说这个错误对味了。

但是新问题来了:

  1. 如果直接是数字编译通不过呀!
  2. 就算是Object类型的参数,我这里还需要强转,可是我的代码没有强转呀!
  3. 还有就是我报错的方法的参数泛型为Map<String,String>,泛型限定为String类后都不能接收Integer类型的参数呀!真的吗?



看我在Map<String, String>集合中,存入Integer类型数据_hashmap_06


4、于是在阅读了同事的代码之后,再次定位问题

同事的工作其实就是把前台给的json字符串,反序列化为一个Map<String,String>的集合中。那么问题只能是在这个反序列化了,明明传进去的时候都还有数字,怎么出来就没有数字了?



三、验证问题

1、自己反序列化测试

模拟全过程代码

public class Map泛型兼容 {
public static void main(String[] args) {
// 模拟前台的传给后台的数据
Map<String, Object> map = new HashMap<>();
map.put("key1", 1);
map.put("key2", "a");

String jsonStr = "";
jsonStr = JSON.toJSONString(map);
// 传给后台的json字符串
System.out.println(jsonStr);

// 对字符串进行反序列化
Map<String, String> params = JSON.parseObject(jsonStr, HashMap.class);

for (Map.Entry<String, String> entry : params.entrySet()) {
if (entry.getValue() instanceof String) {
} else {
System.out.println("不是字符串类型");
}
System.out.println(entry.getValue());
}
}
}



看我在Map<String, String>集合中,存入Integer类型数据_json字符串_07


我们惊奇的发现,居然不报错,他居然不报错,他居然不报错,Integer的转化为String居然没问题,并且最终拿到数据的时候也还是Integer类型,我直接好家伙。

然后我就联想了一下json反序列化是通过反射完成的。


2、反射再验证

它居然还真可以,我使用反射向Map<String,String>的对象中添加数据,然后遍历该map对象,但是却发现其value是一个Integer类型。

那么一切就说的通了



看我在Map<String, String>集合中,存入Integer类型数据_json字符串_08

验证代码:

public class TestReflect {
public static void main(String[] args) throws Exception {
Map<String, String> map = new HashMap<>();

Method put = map.getClass().getMethod("put", Object.class, Object.class);
put.invoke(map, "key1", 1);
put.invoke(map, "key2", "value2");
System.out.println(JSON.toJSON(map));

for (Map.Entry<String, String> entry : map.entrySet()) {
if (entry.getValue() instanceof String) {
} else {
System.out.println("该key是其他类型:" + entry.getValue().getClass());
}
}
}
}


3、结论

最终的结论就是,同事在把前台的所有参数传到后台的时候,里面包含了一个value为Integer类型的数据,那类比就是放到了一个Map<String,Object>的map集合中,在对该map进行序列化后,就变成了一个普通的json字符串。

问题的关键就在于反序列这个位置,在对该json字符串反序列化的时候,就出现了问题。但是这个属于运行时异常了。因为我们的json字符串里面含有Integer类型的参数,但是反序列化的对象却是一个String类型,根据前文的演示,在利用反射进行赋值的时候,并不会报错,以至于一切风平浪静。

但当在我们在使用对应参数的时候,问题就来了,因为我们只认反序列化回的String类型的map,但是实际拿到的却是Integer,以至于引发了后续的错误。



那我们应该如何使用呢?



在我看来我们如果无法明确参数是否可能存在多种类型,可以使用Object类型的map接收反序列出来的集合数据,在获取对应参数的时候也不要使用强转的形式,而是使用更加友好的String.valueOf获取对应的内容(其底层就是调用包装类型的toString方法)



看我在Map<String, String>集合中,存入Integer类型数据_反射_09