15

我正在编写一个将连接到服务器并基于一些参数的类,检索一个 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变量看起来像这样。)

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

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

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

4

3 回答 3

42

简短的回答是,您需要移动TypeTokenout of的创建,在创建令牌 () 时Executor绑定Tin ,并将类型令牌传递给构造函数。Response<T>new TypeToken<Response<Language>>() {}Executor

长答案是:

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

例如,考虑:

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

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

在运行时,Java知道 bar包含整数,因为Integer类型是在编译时绑定的ArrayList,所以泛型类型信息保存在IntegerList类文件中。但是,for 的泛型类型信息foo被删除,因此在运行时实际上不可能确定foo应该包含Integers。

所以经常会出现我们需要泛型类型信息的情况,它通常会在运行前被擦除,例如在 GSON 中解析 JSON 数据的情况下。在这些情况下,我们可以利用类型信息在编译时(如IntegerList上面的示例)绑定时保留的事实,使用类型标记,它们实际上只是方便存储泛型类型信息的微小匿名类。

现在到您的代码:

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

在您的Executor类的这一行中,我们创建了一个匿名类(继承自TypeToken),它Response<T>在编译时具有硬编码(绑定)的类型。所以在运行时,GSON 能够确定您想要一个Response<T>. 但它不知道是什么T,因为您没有在编译时指定它!所以 GSON 无法确定它创建ListResponse对象的类型是什么,它只是创建了一个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"

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

对不起,如果那太长了!

于 2013-01-24T16:33:24.843 回答
2

你没有在这个声明response.execute();之后打电话。Executor<Language> executor = new Executor<Language>();您不能在这里使用 java 泛型,但是您可以使用以下代码获得相同的效果。

响应.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());
}
于 2013-01-24T16:36:58.590 回答
0

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

可悲的是,GSON 无法使用反射知道它应该反序列化到哪个类。

于 2013-01-24T16:22:01.263 回答