Java序列化对字段名的影响
前段时间遇到一个问题,序列化之后原本类中的属性名发生了变化,原本isDel序列化之后得到的是del,为此查了一下相关资料,发现和序列化机制有关
在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:
为此我们要看一下POJO中布尔类型变量不同的命名
class Model1 { private Boolean isSuccess; public void setSuccess(Boolean success) { isSuccess = success; } public Boolean getSuccess() { return isSuccess; } } class Model2 { private Boolean success; public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; } } class Model3 { private boolean isSuccess; public boolean isSuccess() { return isSuccess; } public void setSuccess(boolean success) { isSuccess = success; } } class Model4 { private boolean success; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } }
以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律:
- 基本类型自动生成的getter和setter方法,名称都是
isXXX()
和setXXX()
形式的。 - 包装类型自动生成的getter和setter方法,名称都是
getXXX()
和setXXX()
形式的。
我们可以发现,虽然Model3和Model4中的成员变量的名称不同,一个是success,另外一个是isSuccess,但是他们自动生成的getter和setter方法名称都是isSuccess
和setSuccess
。
关于Java Bean中的getter/setter方法的定义其实是有明确的规定的,根据JavaBeans(TM) Specification规定,如果是普通的参数propertyName,要以以下方式定义其setter/getter:
https://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/
public <PropertyType> get<PropertyName>(); public void set<PropertyName>(<PropertyType> a); public boolean is<PropertyName>(); public void set<PropertyName>(boolean m);
可以看boolean类型的变量方法是单独定义的,使用is方式
通过对照这份JavaBeans规范,我们发现,在Model4中,变量名为isSuccess,如果严格按照规范定义的话,他的getter方法应该叫isIsSuccess。但是很多IDE都会默认生成为isSuccess。
在序列化中,这样就会受到影响
@Data class Model3 implements Serializable { private static final long serialVersionUID = 1836697963736227954L; private Integer age; private String name; private boolean isSuccess; private boolean isDel; public String getUser(){ return "Hello JWZ"; } }
我们定义一个类,使用lombok,然后看序列化进行测试
//定一个Model3类型 Model3 model3 = new Model3(); model3.setSuccess(true); //使用fastjson(1.2.46)序列化model3成字符串并输出 System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3)); //使用Gson(2.8.5)序列化model3成字符串并输出 Gson gson =new Gson(); System.out.println("Serializable Result With Gson :" +gson.toJson(model3)); //使用jackson(2.9.6)序列化model3成字符串并输出 ObjectMapper om = new ObjectMapper(); System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));
得到如下结果:
可以看到三种序列化的方式,
fastjson输出有值的数据,包含user,带is的字段被序列化不带is
Gson输出有值的数据,不包含user,带is的字段被序列化正常
Jackson输出所有有值和null的数据,包含user,带is的字段被序列化不带is
由此可以得出结论:
fastjson和Jackson是通过反射遍历getter方法,然后根据JavaBeans规则他会去掉is来获取属性值。
Gson是通过直接反射遍历类中所有属性。
现在我们试一下,对于同一个对象,如果用fastjson序列化,然后在使用Gson反序列化:
public static void main(String[] args) { Model3 model3 = new Model3(); model3.setSuccess(true); String fastJSONString=JSON.toJSONString(model3); System.out.println(fastJSONString); Gson gson =new Gson(); System.out.println(gson.fromJson(fastJSONString,Model3.class)); }
isSuccess竟然变为false
因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,把model对象序列化城字符串后内容为{"success":true}
。
根据{"success":true}
这个json串,Gson框架在通过解析后,通过反射寻找Model类中的success属性,但是Model类中只有isSuccess属性,所以,最终反序列化后的Model类的对象中,isSuccess则会使用默认值false。
因此,应尽量使用success式的命名来从源头避免这个问题。
延伸,布尔类型定义应使用Boolean还是boolean
布尔类型应该使用包装类型还是基本数据类型呢?
public class Test { public static void main(String[] args) { Model3 model3 = new Model3(); System.out.println("model3 : " + model3); } } @Data class Model3 implements Serializable { boolean success; Boolean del; }
包装类型的默认值是null,基本类型的默认值输出了false,这在某些情况就会造成问题,建议在POJO和RPC的返回值中使用包装类型
所以在定义布尔类型变量时,应使用:
Boolean success;
参考:
《Java工程师成神之路》
《阿里巴巴Java开发手册》