跳了一次JAVA泛型擦除的坑
记录一下今天在帮同事解决使用spring参数注入问题的时候由于对泛型的理解不到位而遇到的坑。
如下代码所示:
@RequestMapping(value="saveAll")
public ResponseMsg saveAll(List rules){
Rule rule=rules.get(0); //这行代码在测试的时候报错了
......
}
这段代码的意思是使用spring的参数注入功能自动完成将前端传过来的数据装载到rules变量里面。
我刚开始一直认为这段代码肯定是对的,然后就一直说同事一定是前端传过来的数据有错,然后就各种检查js,debug一步一步的查,发现前端传过来的数据是正确的,后来我又想会不会是eclipse的debug功能有缺陷(原谅一个idea粉对eclipse的各种不屑),当然继续被打脸,因为我在我电脑上debug时数据是一样的,rules里面的元素居然是LinkedHashMap!!看着debug显示的数据,简直不能接受,我明明声明了rules对象只能存Rules对象啊,怎么会装其他对象!!
然后就真的没辙了。。我就说这个问题我解决不了了,超出我认知范围啊,然后我们叫了一个正式员工过来帮我们看看。。他开始也是按我们的步骤排错,后来遇到和我们一样的问题,但是牛人终究是牛人,能想出来的导致问题的因素也比我们多,他说会不会是spring不支持这种带泛型的自动参数装载啊,毕竟泛型是要被擦除的。。
擦除。。。泛型擦除。。。我靠,我终于知道是什么原因了。之前看了那么多关于泛型擦除的居然都没有想到是这个问题!!而且这种坑当时也踩过,居然没联想起来,智商捉急。
关于泛型擦除的详细介绍具体是什么我就不写在这篇文章里面了,大概就是在编译前会执行一系列的语法检查,从而减少因为强制类型转换带来的异常,但是编译后的代码是不含泛型的,会将泛型限制的元素类型给去掉。
也就是说虽然我声明了rules只能装Rule类型的对象,但是代码被编译后,这个限制就没有了!因为通过语法检查rules里的元素确实是Rule类型的对象,所以并不需要在编译后再去检查。但是问题来了,这种检查只能检查一些显式生命的对象是不是Rule类型,而java是可以通过反射来动态的生成对象的,sprng在参数注入的时候是通过反射实现前端参数自动装载入对象的相关属性!!
所以这样声明的问题在于,由于编译时对rules内元素类型的限制已经被擦除了,所以spring并不知道反射成那种类型的对象,于是就默认的用LinkedHashMap来装载一个对象所有的属性和值,于是rules里面的对象在运行的时候实际上是LinkedHashMap!!!所以spring可能并不支持泛型参数或者需要指定其他条件才能正确的注入泛型参数(这个还没有深究)。
至于以前踩过这方面的坑就是用Gson反序列化带泛型的对象的时候需要额外指定一个参数来说明集合里面的元素类型(具体的我忘了,这个有思路就好)。当时也是觉得很奇妙,为什么不做得智能一点自己识别,我不是已经通过泛型指定类型了么。当时也就抱怨一下,没有怎么多想,现在想起来还真是too young , too simple。。
这件事让我明白不要盲目的相信自己的经验,计算机肯定是对的。经验解决不了的问题,就从原理一步一步去想,平时学的理论可能看起来没什么用,就好像科普一样,然而在解决一些问题时确是一针见血。多联想,发散思维才能在技术这条路上走得更远。