FastJson中如何传递泛型参数以及关于泛型擦除的一点思考

1 篇文章 0 订阅

一、问题说明

现在需要将json字符串转换成带泛型的javaBean(如:List<Student>,List<String>等)

里面的泛型参数不确定,能否将Student、String这些作为参数传到方法里来。

换句话说,能否就传递Student.class、String.class,让fastjson把json字符串转换成相应的对象

public class Student {

    private String sid; // 学生id
    private String sname; // 学生姓名
    private int sage; // 学生年龄

	// 省略构造方法、setter和getter方法
}

代码举例:

如果转换成List<String>,就写成

String listJsonData1 = "[\"张三\",\"李四\",\"王五\"]";
List<String> list1 = JSON.parseObject(listJsonData1, new TypeReference<List<String>>(){});
System.out.println(list1);

如果转换成List<String>,就写成

String listJsonData2 = "[{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}]";
List<Student> list2 = JSON.parseObject(listJsonData2, new TypeReference<List<Student>>(){});
System.out.println(list2);

但是能不能提取成一个方法,只要传入类型形参和json字符串呢?

二、解决方法

直接上解决方案,然后再做说明

public class FastJsonUtil {

	/**
     * 把json字符串转换成带泛型的javaBean
     * @param jsonStr json字符串
     * @param actualArguments 类型参数
     * @param rawType 声明泛型的类
     * @param <T>
     * @return
     */
    public static <T> T convertToBean(String jsonStr, Type[] actualArguments, Type rawType) {
        Type type = new ParameterizedTypeImpl(actualArguments, null, rawType);
        return JSON.parseObject(jsonStr, type);
    }
}

如何调用呢,上测试方法:

@Test
public void test() {
	// 转换成List<String>
	String listJsonData1 = "[\"张三\",\"李四\",\"王五\"]";
	List<String> list1 = FastJsonUtil.convertToBean(listJsonData1, new Type[]{String.class}, List.class);
	System.out.println(list1);

	// 转换成List<String>
	String listJsonData2 = "[{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}]";
	List<Student> list2 = FastJsonUtil.convertToBean(listJsonData2, new Type[]{Student.class}, List.class);
	System.out.println(list2);
}

那如果是其他形式的泛型类呢?比如:Map<Integer,Student>

我们知道,如果直接用TypeReference的话,写法如下:

String mapJsonData = "{\"1\":{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},\"2\":{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}}";
Map<Integer, Student> map = JSON.parseObject(mapJsonData, new TypeReference<Map<Integer, Student>>(){});
System.out.println(map);

那如果用上面封装的工具方法呢?写法如下:

@Test
public void test2() {
	// 转换成Map<Integer, Student>
	String mapJsonData = "{\"1\":{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},\"2\":{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}}";
	Map<Integer, Student> map = FastJsonUtil.convertToBean(mapJsonData, new Type[]{Integer.class, Student.class}, Map.class);
	System.out.println(map);
}

三、解释说明

有关泛型的一些相对冷门的知识,可以参看https://www.jianshu.com/p/e8eeff12c306(mybatis框架源码中大量使用反射方法,其实通过反射获取泛型,里面就用得很多)

比如:

我和很多人一样,刚开始学java泛型的时候听说过“泛型擦除”。但是到了fastjson上,竟然并没有被擦除,这点让我一直非常疑惑。

先上个例子:

@Test
public void test3() {
	List<String> list1 = new ArrayList<String>();
	List<Integer> list2 = new ArrayList<Integer>();
	System.out.println(list1.getClass() == list2.getClass());
}

打印的结果为true(通过该类本身的构造器生成实例,并且不是生成匿名子类的情况下,泛型是会被擦除的)

我们再仔细看下fastjson里的调用,比如转换成List<String>的那段代码

String listJsonData1 = "[\"张三\",\"李四\",\"王五\"]";
List<String> list1 = JSON.parseObject(listJsonData1, new TypeReference<List<String>>(){});
System.out.println(list1);

可以发现,parseObject传的第二个参数最后面有个大括号,这是什么意思呢?这样传参其实传的是TypeReference的匿名子类(https://www.iteye.com/problems/91634),这个TypeReference本身没有太多特别之处(https://www.jianshu.com/p/0dc13273e931),从这个写法上来看,我们可以自己把test3稍加改动下,就得到不一样的效果,同时还能打印出类型形参对应的class

@Test
public void test4() {
	List<String> list1 = new ArrayList<String>(){};
	List<Integer> list2 = new ArrayList<Integer>(){};
	// 判断两个子类的类型是否相同(false)
	System.out.println(list1.getClass() == list2.getClass());
	// 判断list1和list2是否为List的子类或子接口(都为true)
	System.out.println(List.class.isAssignableFrom(list1.getClass()));
	System.out.println(List.class.isAssignableFrom(list2.getClass()));

	// 获取父类声明的类型(java.util.ArrayList<java.lang.String>和java.util.ArrayList<java.lang.Integer>)
	System.out.println(list1.getClass().getGenericSuperclass());
	System.out.println(list2.getClass().getGenericSuperclass());

	// 获取声明的类型参数(class java.lang.String和class java.lang.Integer)
	ParameterizedType type1 = (ParameterizedType) list1.getClass().getGenericSuperclass();
	ParameterizedType type2 = (ParameterizedType) list2.getClass().getGenericSuperclass();
	System.out.println(type1.getActualTypeArguments()[0]);
	System.out.println(type2.getActualTypeArguments()[0]);
}

以List<String> list1 = new ArrayList<String>(){};为例

现在可以得出一个结论,【泛型不会被擦除的情形之一】如果一个类实例(list1)是声明的泛型类(ArrayList<String>)的子类时,那么这个类实例(list1)的泛型不会被擦除

还有别的情况吗?再举个例子

public class TypeParamStudy {
    @Test
    public void testGetActualTypeArguments() throws NoSuchFieldException {
        Field fieldMap = ParameterizedTypeTest.class.getDeclaredField("map");
        Type typeMap = fieldMap.getGenericType();
        ParameterizedType parameterizedTypeMap = (ParameterizedType) typeMap;
        // 获取泛型中的实际类型(class java.lang.String,class java.lang.Integer)
        Type[] types = parameterizedTypeMap.getActualTypeArguments();
        System.out.println(types[0]);
        System.out.println(types[1]);
    }
}


class ParameterizedTypeTest<T> {
    private Map<String, Integer> map = null;
}

【泛型不会被擦除的情形之二】(以testGetActualTypeArguments单元测试为例)如果该泛型类(Map<String, Integer>)作为另外一个类(ParameterizedTypeTest)的属性,那么该属性的泛型(String, Integer)不会被擦除

以上纯属个人理解,如有错误的地方,还望多多指正,谢谢。

参考链接:

https://www.jianshu.com/p/e8eeff12c306

https://www.iteye.com/problems/91634

https://www.jianshu.com/p/0dc13273e931

附测试代码:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.kittycoder.po.Student;
import org.junit.Test;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by luoxiaoshu on 2019-5-18 上午 0:29
 */
public class FastJsonUtilTest {

    /**
     * 不使用convertToBean时的常规写法
     */
    @Test
    public void test1() {
        String listJsonData1 = "[\"张三\",\"李四\",\"王五\"]";
        List<String> list1 = JSON.parseObject(listJsonData1, new TypeReference<List<String>>(){});
        System.out.println(list1);

        String listJsonData2 = "[{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}]";
        List<Student> list2 = JSON.parseObject(listJsonData2, new TypeReference<List<Student>>(){});
        System.out.println(list2);

        // 转换成Map<Integer, Student>
        String mapJsonData = "{\"1\":{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},\"2\":{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}}";
        Map<Integer, Student> map = JSON.parseObject(mapJsonData, new TypeReference<Map<Integer, Student>>(){});
        System.out.println(map);
    }
    
    /**
     * 测试convertToBean工具方法
     */
    @Test
    public void convertToBean() {
        // 转换成List<String>
        String listJsonData1 = "[\"张三\",\"李四\",\"王五\"]";
        List<String> list1 = FastJsonUtil.convertToBean(listJsonData1, new Type[]{String.class}, List.class);
        System.out.println(list1);

        // 转换成List<String>
        String listJsonData2 = "[{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}]";
        List<Student> list2 = FastJsonUtil.convertToBean(listJsonData2, new Type[]{Student.class}, List.class);
        System.out.println(list2);

        // 转换成Map<Integer, Student>
        String mapJsonData = "{\"1\":{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},\"2\":{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}}";
        Map<Integer, Student> map = FastJsonUtil.convertToBean(mapJsonData, new Type[]{Integer.class, Student.class}, Map.class);
        System.out.println(map);
    }

    // 泛型擦除
    @Test
    public void test3() {
        List<String> list1 = new ArrayList<String>();
        List<Integer> list2 = new ArrayList<Integer>();
        System.out.println(list1.getClass() == list2.getClass());
    }

    // 泛型未被擦除
    // 【泛型不会被擦除的情形之一】如果一个类实例(list1)是声明的泛型类(ArrayList<String>)的子类时,
    // 那么这个类实例(list1)的泛型不会被擦除
    @Test
    public void test4() {
        List<String> list1 = new ArrayList<String>(){};
        List<Integer> list2 = new ArrayList<Integer>(){};
        // 判断两个子类的类型是否相同(false)
        System.out.println(list1.getClass() == list2.getClass());
        // 判断list1和list2是否为List的子类或子接口(都为true)
        System.out.println(List.class.isAssignableFrom(list1.getClass()));
        System.out.println(List.class.isAssignableFrom(list2.getClass()));

        // 获取父类声明的类型(java.util.ArrayList<java.lang.String>和java.util.ArrayList<java.lang.Integer>)
        System.out.println(list1.getClass().getGenericSuperclass());
        System.out.println(list2.getClass().getGenericSuperclass());

        // 获取声明的类型参数(class java.lang.String和class java.lang.Integer)
        ParameterizedType type1 = (ParameterizedType) list1.getClass().getGenericSuperclass();
        ParameterizedType type2 = (ParameterizedType) list2.getClass().getGenericSuperclass();
        System.out.println(type1.getActualTypeArguments()[0]);
        System.out.println(type2.getActualTypeArguments()[0]);
    }
}

 

20190702更新:

test2中转换成Map<Integer, Student>,还可以写成(见下面的写法二)

@Test
public void test2() {
	// 转换成Map<Integer, Student>
	// 写法一:
	String mapJsonData = "{\"1\":{\"sname\":\"张三\",\"sage\":10,\"sid\":\"1\"},\"2\":{\"sname\":\"李四\",\"sage\":20,\"sid\":\"2\"}}";
	Map<Integer, Student> map = FastJsonUtil.convertToBean(mapJsonData, new Type[]{Integer.class, Student.class}, Map.class);
	System.out.println(map);

	// 写法二:(可以稍微作下封装)
	Map<Integer, Student> map2 = JSON.parseObject(mapJsonData, new HashMap<Integer, Student>(){}.getClass().getGenericSuperclass());
	System.out.println(map2);
}

 

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值