Gson从反射字段类型

时间:2017-05-30 18:06:28

标签: java list generics serialization gson

我遇到了将List类型从反射对象转换为该对象的set方法的问题。

我不确定问题是在前端还是在Gson解析端,但我的测试代码在这里:

public static void main(String[] args) throws Exception{
    String json = "[52881]";
    ListObject lo = new ListObject();
    for(Field f : lo.getClass().getDeclaredFields())
        if(List.class.isAssignableFrom(f.getType())){
            Method m = lo.getClass().getMethod(
                    new StringBuilder("set").append(f.getName().substring(0,1).toUpperCase()).append(f.getName().substring(1,f.getName().length())).toString(),
                    f.getType()
            );
            System.out.println(((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0]);
            getListFromJson(m, lo, json, 
                    ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0].getClass());
        }
    ListObject p = (ListObject) lo;
    System.out.println(gson.toJson(p.getAList()));
    for(long pid : p.getAList()){
        System.out.println(pid);
    }
}

@SuppressWarnings("unchecked")
public static <T> void getListFromJson(Method m, Object target, String json, Class<T> elementType) throws Exception {
        m.invoke(target, (List<T>)gson.fromJson(json, new TypeToken<List<T>>(){}.getType()));
}

ListObject:

public class ListObject {

    List<Long> aList = new ArrayList<Long>();

    public List<Long> getAList() {
        return aList;
    }

    public void setAList(List<Long> aList) {
        this.aList = aList;
    }
}

此测试程序产生以下输出:

  

class java.lang.Long

     

[52881.0]

     

线程中的异常&#34; main&#34; java.lang.ClassCastException:java.lang.Double无法强制转换为java.lang.Long       在test.GenericTester.main(GenericTester.java:66)

第66行是

for(long pid : p.getAList()){

我在这里使用这个答案来获取getList方法: https://stackoverflow.com/a/18321048/2555197

1 个答案:

答案 0 :(得分:3)

这是因为Java泛型的实现方式:<T>只能存在于编译器的脑海中或类文件元数据中。让我们仔细看看并进行简要分析:

@SuppressWarnings("unchecked")
public static <T> void getListFromJson(Method m, Object target, String json, Class<T> elementType) throws Exception {
        m.invoke(target, (List<T>)gson.fromJson(json, new TypeToken<List<T>>(){}.getType()));
}
    由于Java泛型限制,
  • Class<T> elementType根本不玩这里。这只是尝试对该方法设置一些通用限制,但是如果你删除了参数,那么什么都不会改变。此外,Class<T>只能保留原始类型,即使您使用未经检查的强制转换来使Class<T>保持List<List<List<String>>>之类的内容,您仍然会获得最顶层的原始类型{{1你可以删除参数(但是,请按照下面描述的方案保留它)。
  • 类型标记基于JVM允许存储超类参数化的事实。您可能会注意到,类型标记使用在编译时已知的类型参数进行参数化:Listnew TypeToken<String> - 在这种情况下,编译器可以基于已知类型生成正确的超级参数化。这些类型用于类型令牌。 new TypeToken<List<Integer>>在编译时是 unknown 。在运行时检查它:<T> - 输出为System.out.println(new TypeToken<T>() {}.getType());。什么是T?只是一个通配符,而不是真正的类型。
  • 您必须了解令牌的类型:如果有类似T(非法而非List<Integer>.type!)的内容,它们只是Java支持的便捷方式。类型标记只是分析它们的参数化是可能.class个实例的返回Type个实例。此外,你可以创建你自己的ParameterizedType没有类型标记,因为这些只是定义一些getter的接口。因此,为了动态构建类型标记:
    • 如果您使用的是Gson 2.8.0,请使用ParameterizedType;
    • 如果你有一个较旧的Gson版本或者你想尽可能地控制它,只需自己创建并返回一个TypeToken.getParameterized(List.class, elementType).getType()实例:
ParameterizedType

非常自我描述,不是吗?

  • 为什么Gson会返回new ParameterizedType() { @Override public Type getRawType() { return List.class; } @Override public Type[] getActualTypeArguments() { return new Type[]{ elementType }; } @Override public Type getOwnerType() { return null; } } 而不是Double?拥有Long的通配符,Gson使用默认策略,就好像该类型是完全未知的一样:默认情况下,所有数字都使用双精度数,因为它们可以保留所有标准Java数值。这就是为什么只在取消引用列表元素时获得强制转换异常的原因(但不是列表 - 局部变量不存在类型参数。)

很抱歉这么模糊的解释,但总结一下,你只需要以下方法:

<T>

注意,现在static <T> void getListFromJson(final Method method, final Object target, final String json, final Type elementType) throws InvocationTargetException, IllegalAccessException { final List<T> ts = gson.fromJson(json, TypeToken.getParameterized(List.class, elementType).getType()); method.invoke(target, ts); } 参数在游戏中玩,类型标记用于创建elementType实例,Gson现在可以使用反序列化策略用于ParameterizedType定义的类型( elementType在那个地方)。

输出:

  

class java.lang.Long
  [52881]
  52881