GSON 期间的 Java 类型擦除

问题描述 投票:0回答:1
public static void main(String[] args) {
    String tr = "[{\"key\":\"foo\"}, {\"key\":\"shoe\"}]";

    List<MyClass> o = new Gson().fromJson(tr, getType());
    for (MyClass object : o) {
        System.out.println(object);
    }
}

public static <T extends MyClass> Type getType() {
    return new TypeToken<List<T>>() {
    }.getType();
}

public static class MyClass {
    private String key;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    @Override
    public String toString() {
        return "X{" +
                "key='" + key + '\'' +
                '}';
    }
}

我试图了解 Gson 如何与泛型一起工作。 我在上面的主要方法中遇到错误。

线程“main”中的异常 java.lang.ClassCastException:类 com.google.gson.internal.LinkedTreeMap 无法转换为类 org.example.Main$MyClass(com.google.gson.internal.LinkedTreeMap 和 org.example. Main$MyClass 位于加载程序“app”的未命名模块中)

由于类型擦除,不应在 getType() 方法中将 T 设置为 MyClass。那么,Gson 应该创建一个

List<MyClass>
的实例而不是
List<Object>
吗?为什么 T 在这里被视为对象而不是 MyClass?

我尝试运行此代码,但它给了我错误。

gson type-erasure
1个回答
0
投票

你是对的,这种行为与正常的 Java 类型擦除不匹配。然而,Gson 实际上看到了类型变量

T
,然后自己对其执行(不正确的)类型擦除。 Gson 目前忽略类型变量的边界,并始终使用
Object
代替,请参阅 issue 2563

这会导致您看到的异常:Gson 将值反序列化为

Object
,并且因为它是一个 JSON 对象,Gson 创建一个
Map
,更具体地说是其内部
LinkedTreeMap
类的实例。

作为旁注,您的代码包含两个非类型安全操作:

  • 创建一个

    TypeToken<List<T>>

    正如您在运行时提到的,由于类型擦除,Gson 无法知道
    T
    的实际类型。因此,如果您的示例有一个
    MySubclass extends MyClass
    ,那么即使 Gson 的类型擦除是正确的,当您调用
    ClassCastException
    时,您仍然可以看到
    <MySubclass>getType()
    。因为在运行时此信息不可用,所以它实际上是一个
    TypeToken<List<T extends MyClass>>
    ,被删除为
    TypeToken<List<MyClass>>
    而不是
    TypeToken<List<MySubclass>>

    由于未来的 Gson 版本将默认不允许捕获类型变量

  • 使用

    Gson.fromJson(..., Type)

    这些方法无法强制执行类型安全,并且允许您编写类似以下内容而不会导致任何编译警告或错误:

    List<MyClass> l = gson.fromJson(json, new TypeToken<List<WrongClass>>() {}.getType());
    

    (注意

    MyClass
    WrongClass
    不匹配)

    您应该更喜欢 Gson 2.10 中添加的类型安全

    Gson.fromJson(..., TypeToken<T>)
    重载。也就是说,只需省略
    TypeToken.getType()
    调用即可。

getType()
方法的类型安全变体可能如下所示:

private static <T extends MyClass> TypeToken<List<T>> getType(Class<T> elementClass) {
  return (TypeToken<List<T>>) TypeToken.getParameterized(List.class, elementClass);
}

在 Kotlin 中,如果您 创建类型变量

reified
并将返回类型从
Type
更改为
TypeToken<List<T>>
,则原始示例也可以工作。

© www.soinside.com 2019 - 2024. All rights reserved.