【问题标题】:Strange behavior when deserializing nested, generic classes with GSON使用 GSON 反序列化嵌套的泛型类时的奇怪行为
【发布时间】:2013-01-08 08:36:41
【问题描述】:

我正在编写一个类,它将连接到服务器并基于一些参数,检索一个 json 字符串,该字符串将使用 GSON 解析到指定的(通过泛型)类。

负责班级的精简版如下所示:

class Executor<T> {

    private Response<T> response;

    public void execute() {
        Type responseType = new TypeToken<Response<T>>() {}.getType();
        this.response = new Gson().fromJson(json, responseType);
    }

    public Response<T> getResponse() { return this.response; }

}

(JSON-变量looks like this。)

反序列化后存储数据的类如下所示:

class Response<T> {

    private List<T> data = null;

    public List<T> getData() { return this.data; }

}

数据试图反序列化到的类:

public class Language {
    public String alias;
    public String label;
}

并且运行的代码利用了上面的类:

Executor<Language> executor = new Executor<Language();
List<Language> languages = executor.execute().getResponse().getData();
System.out.println(languages.get(0).alias); // exception occurs here

这会导致以下异常

ClassCastException:com.google.gson.internal.StringMap 无法转换为 sunnerberg.skolbibliotek.book.Language

非常感谢任何帮助或建议!

【问题讨论】:

  • 你确定异常发生在这一行吗?我看不到在您提供的示例代码中调用 execute() 的位置。
  • @SamuelEUSTACHI 哦,我的错。问题已编辑,当我粘贴代码时它滑出(有点简化)。是的,我 100% 确定,它恰好发生在该行(至少根据堆栈跟踪)。
  • @Zar executor = new Executor

标签: java oop generics reflection gson


【解决方案1】:

您的问题与 java 类型擦除有关。泛型仅在编译时已知。

很遗憾,GSON 无法使用反射知道它应该反序列化到哪个类。

【讨论】:

  • 嗯,好的。有什么办法可以克服这个吗?我想我可以使用替代的类结构,只要我不必为每个 Executor 对创建一个新类。
【解决方案2】:

简短的回答是您需要将TypeToken的创建移出Executor,在创建令牌(new TypeToken&lt;Response&lt;Language&gt;&gt;() {})时将T绑定在Response&lt;T&gt;中,并传入类型Executor 构造函数的令牌。

长答案是:

一个类型的泛型通常在运行时被删除,除非该类型是编译并绑定了泛型参数。在这种情况下,编译器会将泛型类型信息插入到编译后的类中。在其他情况下,这是不可能的。

例如,考虑:

List<Integer> foo = new ArrayList<Integer>();

class IntegerList extends ArrayList<Integer> { ... }
List<Integer> bar = new IntegerList();

在运行时,Java 知道 bar 包含整数,因为Integer 类型在编译时绑定到ArrayList,所以泛型类型信息保存在IntegerList 类文件中.但是,foo 的泛型类型信息已被删除,因此在运行时无法确定 foo 是否应该包含 Integers。

因此,我们经常需要泛型类型信息,因为它通常会在运行前被删除,例如在 GSON 中解析 JSON 数据的情况。在这些情况下,我们可以利用 type tokens 在编译时绑定类型信息时保留类型信息(如上面的 IntegerList 示例)这一事实,它们实际上只是很小的匿名类,可以方便地存储泛型类型信息。

现在到你的代码:

Type responseType = new TypeToken<Response<T>>() {}.getType();

在Executor 类的这一行中,我们创建了一个匿名类(继承自TypeToken),它在编译时具有Response&lt;T&gt; 硬编码(绑定)类型。因此,在运行时,GSON 能够确定您想要Response&lt;T&gt; 的对象。但它不知道T 是什么,因为你没有在编译时指定它!所以 GSON 无法确定它创建的 Response 对象的 List 中的类型,它只是创建了一个 StringMap。

这个故事的寓意是你需要在编译时指定T。如果Executor 是为了通用使用,您可能需要在该类之外的客户端代码中创建类型标记。比如:

class Executor<T> {

    private TypeToken<Response<T>> responseType;
    private Response<T> response;

    public Executor(TypeToken<Response<T>> responseType) {
        this.responseType = responseType;
    }

    public void execute() {
        this.response = new Gson().fromJson(json, responseType.getType());
    }

    public Response<T> getResponse() { return this.response; }

}

// client code:
Executor<Language> executor = new Executor<Language>(new TypeToken<Response<Language>>() { });
executor.execute();
List<Language> languages = executor.getResponse().getData();
System.out.println(languages.get(0).alias); // prints "be"

顺便说一句,我确实在我的机器上测试了上述内容。

抱歉,如果太长了!

【讨论】:

  • 首先,我必须说 StackOverflow 的用户永远不会停止让我惊讶。哇,多么好的答案。正是我正在寻找的(和需要的!),非常感谢你!您的解决方案完美地解决了我的问题。顺便问一下,JVM擦除类型信息有什么原因吗?
  • 嗯,感谢您提供详细而完整的问题!至于类型擦除,我相信 Java 这样做是为了向后兼容。使用泛型的编译代码看起来很像不使用泛型的编译代码。例如,如果您有一些使用 SomeLibrary 1.0 的旧代码,您可以切换到 SomeLibrary 2.0 (Now With Generics) 而无需重新编译您的代码。查看此blog post 以获得其他解释。
  • 我觉得有道理,再次感谢!顺便说一句,有趣的链接。
  • 响应来自哪个包?
  • 非常感谢,刚刚结束了第一天的 Kotlin 和 Java 体验,您的回答节省了很多时间:D
【解决方案3】:

在Executor&lt;Language&gt; executor = new Executor&lt;Language&gt;();这个声明之后你还没有打电话给response.execute();。你不能在这里使用java泛型,但是你可以用下面的代码得到同样的效果。

Response.java

import java.io.Serializable;
import java.util.List;

/**
 *
 * @author visruth
 */
public class Response<T> implements Serializable {

    private List<T> data = null;

    public List<T> getData() {
        return this.data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }

}

语言.java

import java.io.Serializable;

/**
 *
 * @author visruth
 */
public class Language implements Serializable {
    private String alias;
    private String label;

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

}

最后是 Executor.java

import com.google.gson.Gson;
import java.util.*;

/**
 *
 * @author visruth
 */
public class Executor<T> {
private Response<T> response;

    public Response<T> getResponse() {
        return response;
    }

    /**
     * @param args the command line arguments
     */
    public void executor() {
        //sample data
        Response<Language> response=new Response<Language>();
        Language lan1=new Language();
        lan1.setAlias("alias1");
        lan1.setLabel("label1");
        Language lan2=new Language();
        lan2.setAlias("alias2");
        lan2.setLabel("label2");
        List<Language> listOfLangauges=new ArrayList<Language>();
        listOfLangauges.add(lan1);
        listOfLangauges.add(lan2);
        response.setData(listOfLangauges);
        Gson gson=new Gson();
        String json = gson.toJson(response);

        System.out.println(json);
        Response<Language> jsonResponse = gson.fromJson(json, Response.class);
        List list=jsonResponse.getData();

        List<Language> langs=new ArrayList<Language>();            
        for(int i=0; i<list.size(); i++) {            
        Language lan=gson.fromJson(list.get(i).toString(), Language.class);
        langs.add(lan);
        //System.out.println(lan.getAlias());
        }
        Response<Language> responseMade=new Response<Language>();
        responseMade.setData(langs);
        this.response=(Response<T>) responseMade;

    }
}

可以如下测试

Executor<Language> executor = new Executor<Language>();
executor.executor();
List<Language> data = executor.getResponse().getData();
for(Language langu: data) {
    System.out.println(langu.getAlias());
    System.out.println(langu.getLabel());
}

【讨论】:

  • 非常感谢您的回答,它给了我一些(希望是)好主意!
猜你喜欢
  • 1970-01-01
  • 2013-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多