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

Zar*_*Zar 10 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; }

}
Run Code Online (Sandbox Code Playgroud)

(JSON变量看起来像这样.)

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

class Response<T> {

    private List<T> data = null;

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

}
Run Code Online (Sandbox Code Playgroud)

数据尝试反序列化的类:

public class Language {
    public String alias;
    public String label;
}
Run Code Online (Sandbox Code Playgroud)

运行的代码使用上面的类:

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

这导致以下异常

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

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

mat*_*tts 29

简短的回答是你需要移动创建TypeTokenout Executor,TResponse<T>创建token(new TypeToken<Response<Language>>() {})时绑定in ,并将类型标记传递给Executor构造函数.

答案很长的答案是:

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

例如,考虑一下:

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

class IntegerList extends ArrayList<Integer> { ... }
List<Integer> bar = new IntegerList();
Run Code Online (Sandbox Code Playgroud)

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

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

现在到你的代码:

Type responseType = new TypeToken<Response<T>>() {}.getType();
Run Code Online (Sandbox Code Playgroud)

在这个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"
Run Code Online (Sandbox Code Playgroud)

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

对不起,如果太长了!

  • 首先,我必须说StackOverflow的用户永远不会让我惊讶.哇,真是个答案.正是我所寻找的(并且需要!),非常感谢你!你的解决方案完美地解决了我 那么,JVM是否有任何理由删除类型信息? (3认同)