我们可以依靠 Gson 修改 val 属性的能力吗?

Can we rely on Gson's ability to modify val attributes?

Kotlin 的 val 属性在设计上是不可变的。它们应该在初始化后固定不变。

然而,我无意中发现Gson可以修改那些属性。

考虑这个例子:

//Entity class:
class User {
    val name: String = ""
    val age: Int = 0
}

由于 nameage 被定义为 val 并被初始化为空字符串和零,因此它们必须保持不变并且永远不会改变。

但是,这个测试成功了:

fun main() {
    val json = "{\"name\":\"Mousa\",\"age\":30}"
    val user = Gson().fromJson<User>(json, User::class.java)
    assert(user.name == "Mousa")
    assert(user.age == 30)
    print("Success")
}

使用此功能可以使代码更简洁。因为那些属性是由 Gson 修改的,我们的其余代码无法修改。否则,我需要为 Gson 提供一些可变的支持字段,为其余代码提供一些不可变的属性。

我不知道这是 Gson 的 "Feature" 还是 "Bug"。所以我的问题是:我们可以依赖这个吗,或者这个行为以后可能会改变?

如果你 运行 javap -p User.class,你会得到以下输出:

public final class q54491286.User {
    private final java.lang.String name;
    private final int age;
    public final java.lang.String getName();
    public final int getAge();
    public q54491286.User();
}

Kotlin 不可变性不受为(隐式)声明 privatefinal 的字段生成的增变器方法的支持。 除非您定义自定义类型适配器,否则 Gson 按照设计在序列化和反序列化期间分别不使用访问器和修改器 by design for plain data object classes (See more here Using fields vs getters to indicate Json elements 节)。 此外,Gson 可以修改 final 字段,这也是一种设计选择。

由于反射的大量使用,不可变性在 Java 中是一个 相对 的东西。 例如,我可以使用反射轻松修改不可变(按设计)字符串,使其成为一种可变字符串(通过 intention):

final String s = "foo";
final Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(s, new char[]{ 'b', 'a', 'r' });
System.out.println(s);

当使用来自 java version "1.8.0_191"java 时,这会产生 bar。 没有不变性,呵呵。

另一方面,如果你的 class 可以被 Gson 类型适配器策略检测到以通用方式重新定义方法(Gson 禁止重新定义 ObjectJsonElement 类型适配器),您将能够编写一个自定义反序列化程序,禁止 Kotlin 生成的 classes(由 kotlin.Metadata 注释检测到)反序列化只读字段(不确定它是否有好处, 尽管)。 或者只是实现一个反序列化器,它将扫描 JSON stream/tree 的非只读字段,并仅在使用其合法构造函数不使用反射构造新的 User 对象时使用它们。

I don't know if this is a "Feature" of Gson or a "Bug".

总结起来,就是一个特点。

So my question is: can we rely on this, or this behavior might change later?

这很可能在 Gson 中永远不会改变 2.x。 据我所知,没有任何计划发布重大更新,Gson 3.x 等等。