关于Java的TypeReference
我们知道,Java中的泛型是编译期的,在运行时其会被擦除掉,比如我们编写代码List<Integer> lst = new ArrayList<>();
,从运行时看来将会是List lst = new ArrayList();
,只留下了原始类型(raw type)。
但我们有时候确实需要在运行时获取一定的泛型信息。考虑这样的情况:在一个servlet应用里(为什么是servlet?因为spring mvc遇不到这个问题),我们要求前端使用JSON来发送请求,并规定了请求的格式——
为此,对应的POJO为——
在servlet中,我们需要将请求体字符串转换为特定的RequestDto<T>
。比如某个接口要求前端发送RequestDto<List<Integer>>
。我们在servlet中可能得这么写——
但这个通不过编译——所谓的RequestDto<List<Integer>>.class
是不存在的,因为在运行时不存在泛型类型,我们只能得到RequestDto.class
,所以只能这么写——
虽然有个恼火的警告,但至少能编译了。我们整个demo试试——
成了!我们再试试错误的输入?
抛异常了!这符合预期,但是却是在req.getData()
时抛的cast异常,而非json转换时抛出异常。
这是肿么回事呢?从运行时看来,我们是在试图将字符串{"type":"search", "data": "hello world!"}
转换成类型RequestDto,即——
这河里吗?可太合理了,既然是Object
,那是任何类型都是可以的了。但这显然是不符合我们的需要的——如果类型的错误必须要在我们使用的时候才能暴露出来,那这和动态类型语言何异?
问题就出在Java的泛型擦除机制。我们有什么手段来规避它吗?库函数的设计者告诉我们,有!
Java的泛型擦除机制实际上至少在两个地方没有擦掉——方法的参数和返回值;继承泛型类的类。
获取其的demo如下——
前者显然为Spring mvc所利用——控制器的接口能够正确处理泛型类,而后者则是所谓的TypeReference所利用的——通过继承的方式来保存泛型信息。我们可以通过匿名实现类来在行内(inline)直接拿到该信息。
这样,我们实际上就能够间接地表示RequestDto<Integer>.class
了。对上面的json反序列化的代码,我们可以使用TypeReference的匿名实现类而非class来保留泛型信息——
我们仍旧会得到一个异常,但这个异常是符合预期的,容易理解的,是在进行反序列化中抛出的!