使用GSON反序列化嵌套的泛型类时的奇怪行为

时间:2013-01-24 14:40:25

标签: java oop generics reflection gson

我正在编写一个将连接到服务器并基于某些参数的类,检索一个json-string,该字符串将使用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

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

3 个答案:

答案 0 :(得分:29)

简短的回答是,您需要将TypeToken的创建移出Executor,在创建令牌时绑定T Response<T> new TypeToken<Response<Language>>() {} Executor 1}}),并将类型标记传递给List<Integer> foo = new ArrayList<Integer>(); class IntegerList extends ArrayList<Integer> { ... } List<Integer> bar = new IntegerList(); 构造函数。

答案很长:

类型上的泛型通常在运行时被擦除,除非类型是编译且带有泛型参数绑定。在这种情况下,编译器将泛型类型信息插入到已编译的类中。在其他情况下,这是不可能的。

例如,考虑一下:

bar

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

因此,经常出现的情况是,在运行时通常会擦除通用类型信息,例如在GSON中解析JSON数据的情况。在这些情况下,我们可以利用以下事实:类型信息在编译时被绑定(如上面的Type responseType = new TypeToken<Response<T>>() {}.getType(); 示例中),使用type tokens,这实际上只是一个小小的匿名方便存储泛型类型信息的类。

现在你的代码:

Executor

TypeToken类的这一行中,我们创建了一个匿名类(继承自Response<T>),它在编译时具有类型Response<T>硬编码(绑定)。因此,在运行时,GSON能够确定您想要一个T的对象。但它不知道List是什么,因为你没有在编译时指定它!因此,GSON无法确定其创建的Response对象的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" ,则可能需要在客户端代码中在该类之外创建类型标记。类似的东西:

{{1}}

顺便说一下,我在我的机器上测试了上面的内容。

很抱歉,如果那太长了!

答案 1 :(得分:2)

response.execute();此声明之后,您尚未调用Executor<Language> executor = new Executor<Language>();。你不能在这里使用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;
    }

}

Language.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());
}

答案 2 :(得分:0)

您的问题与java Type擦除有关。泛型只在编译时才知道。

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