【问题标题】:Gson Parsing Generic List From Reflected Field TypeGson 从反射字段类型解析通用列表
【发布时间】:2017-05-30 18:06:27
【问题描述】:

我在将列表类型从反射对象转换为该对象的 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;
    }
}

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

类 java.lang.Long

[52881.0]

线程“main”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

【问题讨论】:

  • 如错误消息所述,您无法将52881.0 Double 转换为 Long。检查您的输入数据
  • 看你链接的问题的第二个答案,是正确的,接受的答案是错误的。
  • 我也会研究第二个答案。

标签: java list generics serialization gson


【解决方案1】:

这是因为 Java 泛型是如何实现的:&lt;T&gt; 只能存在于编译器的头脑中或类文件元数据中。让我们仔细看看并进行简要分析:

@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&lt;T&gt; elementType 根本不在这里播放。这只是尝试对该方法设置一些通用限制,但如果您删除参数,什么都不会改变。此外,Class&lt;T&gt; 只能保存原始类型,即使您使用未经检查的强制转换使Class&lt;T&gt; 保存类似List&lt;List&lt;List&lt;String&gt;&gt;&gt; 的东西,您仍然会得到最顶层的原始类型List,您可以删除参数(但是,请在下面描述的场景中保留它)。
  • 类型标记基于 JVM 允许存储超类参数化的事实。您可能会注意到类型标记使用编译时已知的类型参数进行参数化:new TypeToken&lt;String&gt;new TypeToken&lt;List&lt;Integer&gt;&gt;——在这种情况下,编译器可以根据 known 类型生成正确的 superlcas 参数化。这些类型用于类型标记。 &lt;T&gt; 在编译时未知。在运行时检查它:System.out.println(new TypeToken&lt;T&gt;() {}.getType());——输出为TT 是什么?只是通配符,不是真正的类型。
  • 您必须了解类型标记的用途:如果存在类似List&lt;Integer&gt;.type(非法而不是.class!)的东西,它们只是Java 可以支持的一种便捷方式。类型标记只是分析它们的参数化是返回Type 实例,可能ParameterizedType 实例。此外,您可以创建自己的 ParameterizedType 而不使用类型标记,因为这些只是定义一些 getter 的接口。因此,为了动态构建类型标记:
    • 如果您使用的是 Gson 2.8.0,请使用 TypeToken.getParameterized(List.class, elementType).getType()
    • 如果您有较旧的 Gson 版本或者您希望尽可能控制它,只需自己创建并返回一个 ParameterizedType 实例:
new ParameterizedType() {
    @Override public Type getRawType() { return List.class; }
    @Override public Type[] getActualTypeArguments() { return new Type[]{ elementType }; }
    @Override public Type getOwnerType() { return null; }
}

非常自我描述,不是吗?

  • 为什么 Gson 返回Doubles 而不是Longs?拥有&lt;T&gt; 的通配符,Gson 使用默认策略,就好像类型完全未知一样:默认情况下对所有数字使用双精度数,因为它们可以保留所有标准 Java 数字值。这就是为什么当您仅取消引用列表元素(而不是列表 - 局部变量不存在类型参数)时会出现强制转换异常。

对不起,解释得这么模糊,但总结一下,你只需要以下方法:

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 参数开始游戏,类型标记用于创建ParameterizedType 实例,Gson 现在可以对elementType 定义的类型使用反序列化策略(在那个地方Long )。

输出:

类 java.lang.Long
[52881]
52881

【讨论】:

  • 太棒了!谢谢,我知道我遗漏了一些小而重要的细节。这也加深了我对泛型类型的理解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-20
  • 1970-01-01
  • 1970-01-01
  • 2013-03-12
  • 2017-01-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多