科特林的val
属性在设计上是不可变的。它们应该是固定的,初始化后不能更改。
但是,我偶然发现Gson
能够修改这些属性。
考虑以下示例:
//Entity class:
class User {
val name: String = ""
val age: Int = 0
}
由于name
和age
被定义为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
的“功能”还是“错误”。所以我的问题是:我们可以依靠它吗,否则这种行为以后可能会改变?
答案 0 :(得分:5)
如果运行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不变性不受为(隐式)声明为private
和final
的字段生成的任何变种器方法的支持。
除非您定义自定义类型适配器,否则Gson在设计上不会对纯数据对象类分别在序列化和反序列化期间by design中使用访问器或变量(请参见使用字段与getter中的更多here以指示Json元素部分)。
另外,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);
使用bar
中的java
时会产生java version "1.8.0_191"
。
没有不变,呵呵。
另一方面,如果可以通过Gson类型适配器策略重定义方法以通用方式检测到您的类(Gson禁止重新定义Object
和JsonElement
类型适配器),能够编写自定义反序列化程序,以禁止Kotlin生成的类(通过kotlin.Metadata
注释检测)对只读字段进行反序列化(尽管不确定是否有好处)。
或者只是实现一个反序列化程序,该反序列化程序将扫描JSON流/树中的非只读字段,并仅在使用合法构造函数构造新的User
对象时使用它们,而不使用反射。
我不知道这是Gson的“功能”还是Bug。
总结一下,这是一个功能。
所以我的问题是:我们可以依靠它吗,否则这种行为以后可能会改变?
这很可能在Gson 2.x中永远不会改变。 据我所知,没有任何计划发布主要更新Gson 3.x等。