关于Java集合类反序列化泛型问题碰到的BUG

BUG再现

public class Test {
    public static void main(String[] args) {
        Set<Long> set = new HashSet<>();
        long l1 = 1;
        long l2 = 2;
        set.add(l1);
        set.add(l2);
        String jsonInfo = JSON.toJSONString(set);
        // 对序列化后的字符串反序列化然后操作数据
        Set set1 = JSON.parseObject(jsonInfo, Set.class);
        set1.remove(l1);
        set1.forEach(System.out::println);
    }
}

以上简单的示例代码,结果却和预想的不一样!

1
2
​
进程已结束,退出代码为 0

为什么会这样呢,为什么反序列化后无法remove掉l1?

关于Java集合的泛型

Java中的泛型通过类型擦除的方式来实现,通俗点理解,就是通过语法糖的形式,在java->.class转换的阶段,将List<String>擦除调转为List的手段。换句话说,Java的泛型只在编译期,Jvm是不会感知到泛型的。

集合的特性

我们都知道Java的集合中是无法存储基本数据类型的,所以Java引入了包装类,并且添加了自动装箱和拆箱机制,使得我们有些时候并不能感知到我们操作的其实不是基本数据类型。

问题解决

所以,答案就显而易见了,通过DeBug可以看到,反序列化后的集合类自动将数字变成了Byte类型,这时候想要删除Long类型的数字,不管是通过hash码还是equals都无法正确匹配。

那么,我们该怎么解决集合类反序列化的泛型问题?

答案就是,使用 TypeReference 来保留泛型信息。

public class Test {
    public static void main(String[] args) {
        Set<Long> set = new HashSet<>();
        long l1 = 1;
        long l2 = 2;
        set.add(l1);
        set.add(l2);
        String jsonInfo = JSON.toJSONString(set);
        // 对序列化后的字符串反序列化然后操作数据
        Set<Long> set1 = JSON.parseObject(jsonInfo, new TypeReference<Set<Long>>() {});
        set1.remove(l1);
        set1.forEach(System.out::println);
    }
}

或者也可以写成:

public class DaoTest {
    public static void main(String[] args) {
        Set<Long> set = new HashSet<>();
        long l1 = 1;
        long l2 = 2;
        set.add(l1);
        set.add(l2);
        String jsonInfo = JSON.toJSONString(set);
        // 对序列化后的字符串反序列化然后操作数据
        TypeReference<Set<Long>> typeReference = new TypeReference<Set<Long>>() {};
        Set<Long> set1 = JSON.parseObject(jsonInfo, typeReference.getType());
        set1.remove(l1);
        set1.forEach(System.out::println);
    }
}
​

这时候输出的结果就是

2
​
进程已结束,退出代码为 0